crew-recommendation-ui 0.0.1
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/README.md +156 -0
- package/dist/Card-C6fF0ZrH.js +197 -0
- package/dist/Card-C6fF0ZrH.js.map +1 -0
- package/dist/dls/index.d.ts +8 -0
- package/dist/dls/index.d.ts.map +1 -0
- package/dist/dls/index.js +25 -0
- package/dist/dls/index.js.map +1 -0
- package/dist/dls/tokens/colors.d.ts +77 -0
- package/dist/dls/tokens/colors.d.ts.map +1 -0
- package/dist/dls/tokens/index.d.ts +215 -0
- package/dist/dls/tokens/index.d.ts.map +1 -0
- package/dist/dls/tokens/spacing.d.ts +46 -0
- package/dist/dls/tokens/spacing.d.ts.map +1 -0
- package/dist/dls/tokens/typography.d.ts +102 -0
- package/dist/dls/tokens/typography.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +60 -0
- package/dist/recommendation/components/Card.d.ts +13 -0
- package/dist/recommendation/components/Card.d.ts.map +1 -0
- package/dist/recommendation/components/index.d.ts +6 -0
- package/dist/recommendation/components/index.d.ts.map +1 -0
- package/dist/recommendation/index.d.ts +9 -0
- package/dist/recommendation/index.d.ts.map +1 -0
- package/dist/recommendation/index.js +6 -0
- package/dist/recommendation/index.js.map +1 -0
- package/dist/recommendation/types.d.ts +71 -0
- package/dist/recommendation/types.d.ts.map +1 -0
- package/dist/typography-zz5GzgjI.js +245 -0
- package/dist/typography-zz5GzgjI.js.map +1 -0
- package/package.json +60 -0
- package/src/dls/index.ts +15 -0
- package/src/dls/tokens/colors.ts +94 -0
- package/src/dls/tokens/index.ts +30 -0
- package/src/dls/tokens/spacing.ts +62 -0
- package/src/dls/tokens/typography.ts +119 -0
- package/src/index.ts +25 -0
- package/src/recommendation/components/Card.tsx +191 -0
- package/src/recommendation/components/index.ts +14 -0
- package/src/recommendation/index.ts +15 -0
- package/src/recommendation/types.ts +87 -0
|
@@ -0,0 +1,191 @@
|
|
|
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';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base Recommendation Card Component
|
|
8
|
+
*
|
|
9
|
+
* This is a generic card that can display any recommendation item.
|
|
10
|
+
* Specific card types (Hotel, Flight, Cab) will extend this.
|
|
11
|
+
*/
|
|
12
|
+
export const Card = <T extends BaseRecommendationItem>({
|
|
13
|
+
item,
|
|
14
|
+
onSelect,
|
|
15
|
+
onExpand,
|
|
16
|
+
isSelected = false,
|
|
17
|
+
isExpanded = false,
|
|
18
|
+
}: RecommendationCardProps<T>) => {
|
|
19
|
+
const handleClick = () => {
|
|
20
|
+
if (onExpand) {
|
|
21
|
+
onExpand(item);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleSelect = (e: React.MouseEvent) => {
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
if (onSelect) {
|
|
28
|
+
onSelect(item);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Get the first image URL
|
|
33
|
+
const imageUrl = item.image_signed_urls?.[0] || item.image?.[0]?.data;
|
|
34
|
+
|
|
35
|
+
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
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{/* Image Section */}
|
|
55
|
+
{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
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{/* Content Section */}
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
padding: spacing[4],
|
|
79
|
+
display: 'flex',
|
|
80
|
+
flexDirection: 'column',
|
|
81
|
+
gap: spacing[2],
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{/* Title */}
|
|
85
|
+
<h3
|
|
86
|
+
style={{
|
|
87
|
+
...textStyles.cardTitle,
|
|
88
|
+
color: colors.neutral[0],
|
|
89
|
+
margin: 0,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{item.title}
|
|
93
|
+
</h3>
|
|
94
|
+
|
|
95
|
+
{/* Subtitle */}
|
|
96
|
+
{item.subtitle && (
|
|
97
|
+
<p
|
|
98
|
+
style={{
|
|
99
|
+
...textStyles.cardSubtitle,
|
|
100
|
+
color: colors.neutral[400],
|
|
101
|
+
margin: 0,
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
{item.subtitle}
|
|
105
|
+
</p>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{/* Tags */}
|
|
109
|
+
{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>
|
|
130
|
+
))}
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{/* 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>
|
|
151
|
+
{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>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
{/* 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
|
+
}}
|
|
178
|
+
>
|
|
179
|
+
{isSelected ? '✓ Selected' : 'Select'}
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
Card.displayName = 'RecommendationCard';
|
|
187
|
+
|
|
188
|
+
export default Card;
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recommendation Components
|
|
3
|
+
* Export all card components from here
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { Card, default as RecommendationCard } from './Card';
|
|
7
|
+
|
|
8
|
+
// Future exports:
|
|
9
|
+
// export { HotelCard } from './HotelCard';
|
|
10
|
+
// export { FlightCard } from './FlightCard';
|
|
11
|
+
// export { CabCard } from './CabCard';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recommendation Listing Module
|
|
3
|
+
*
|
|
4
|
+
* Contains all recommendation card components and types
|
|
5
|
+
* for displaying hotels, flights, cabs, etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Export types
|
|
9
|
+
export * from './types';
|
|
10
|
+
|
|
11
|
+
// Export components
|
|
12
|
+
export * from './components';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for recommendation listing components
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ProductImage {
|
|
6
|
+
data: string;
|
|
7
|
+
url?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface BaseRecommendationItem {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
price: string;
|
|
16
|
+
originalPrice?: string;
|
|
17
|
+
image?: ProductImage[];
|
|
18
|
+
image_signed_urls?: string[];
|
|
19
|
+
recommended?: boolean;
|
|
20
|
+
tags?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Hotel specific
|
|
24
|
+
export interface HotelItem extends BaseRecommendationItem {
|
|
25
|
+
type: 'hotel';
|
|
26
|
+
rating?: string;
|
|
27
|
+
ratingCount?: string;
|
|
28
|
+
location?: string;
|
|
29
|
+
amenities?: string[];
|
|
30
|
+
checkIn?: string;
|
|
31
|
+
checkOut?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Flight specific
|
|
35
|
+
export interface FlightItem extends BaseRecommendationItem {
|
|
36
|
+
type: 'flight';
|
|
37
|
+
airlineName?: string;
|
|
38
|
+
economyType?: string;
|
|
39
|
+
date?: string;
|
|
40
|
+
takeoffTime?: string;
|
|
41
|
+
takeoffLocation?: string;
|
|
42
|
+
landingTime?: string;
|
|
43
|
+
landingLocation?: string;
|
|
44
|
+
duration?: string;
|
|
45
|
+
layoverText?: string;
|
|
46
|
+
isRoundTrip?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Cab specific
|
|
50
|
+
export interface CabItem extends BaseRecommendationItem {
|
|
51
|
+
type: 'cab';
|
|
52
|
+
providerName?: string;
|
|
53
|
+
carType?: string;
|
|
54
|
+
carName?: string;
|
|
55
|
+
pickupLocation?: string;
|
|
56
|
+
dropLocation?: string;
|
|
57
|
+
estimatedTime?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Union type for all recommendation items
|
|
61
|
+
export type RecommendationItem = HotelItem | FlightItem | CabItem;
|
|
62
|
+
|
|
63
|
+
// Props for the recommendation card component
|
|
64
|
+
export interface RecommendationCardProps<T extends BaseRecommendationItem = BaseRecommendationItem> {
|
|
65
|
+
item: T;
|
|
66
|
+
onSelect?: (item: T) => void;
|
|
67
|
+
onExpand?: (item: T) => void;
|
|
68
|
+
isSelected?: boolean;
|
|
69
|
+
isExpanded?: boolean;
|
|
70
|
+
showVote?: boolean;
|
|
71
|
+
voteCount?: number;
|
|
72
|
+
onVote?: (itemId: string) => void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Props for the recommendation listing component
|
|
76
|
+
export interface RecommendationListingProps {
|
|
77
|
+
items: RecommendationItem[];
|
|
78
|
+
type: 'hotel' | 'flight' | 'cab' | 'restaurant' | 'gifting';
|
|
79
|
+
onItemSelect?: (item: RecommendationItem) => void;
|
|
80
|
+
onItemExpand?: (item: RecommendationItem) => void;
|
|
81
|
+
selectedItemId?: string;
|
|
82
|
+
expandedItemId?: string;
|
|
83
|
+
showVoting?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|