mautourco-components 0.2.76 → 0.2.78
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/components/atoms/Chip/Chip.css +32 -1
- package/dist/components/molecules/VehicleSupplement/VehicleSupplement.css +85 -0
- package/dist/components/molecules/VehicleSupplement/VehicleSupplement.d.ts +31 -0
- package/dist/components/molecules/VehicleSupplement/VehicleSupplement.js +101 -0
- package/dist/components/molecules/VehicleSupplement/index.d.ts +2 -0
- package/dist/components/molecules/VehicleSupplement/index.js +1 -0
- package/dist/components/organisms/CarBookingCard/CarBookingCard.css +122 -1
- package/dist/components/organisms/CarBookingCard/CarBookingCard.d.ts +12 -6
- package/dist/components/organisms/CarBookingCard/CarBookingCard.js +87 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/components/atoms/Chip/Chip.css +32 -1
- package/src/components/molecules/VehicleSupplement/VehicleSupplement.css +81 -0
- package/src/components/molecules/VehicleSupplement/VehicleSupplement.tsx +206 -0
- package/src/components/molecules/VehicleSupplement/index.ts +2 -0
- package/src/components/organisms/CarBookingCard/CarBookingCard.css +121 -1
- package/src/components/organisms/CarBookingCard/CarBookingCard.tsx +154 -36
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { TransferDocket } from '../../../types/docket/services.types';
|
|
2
3
|
import Button, { ButtonProps } from '../../atoms/Button/Button';
|
|
4
|
+
import Chip from '../../atoms/Chip/Chip';
|
|
3
5
|
import Divider from '../../atoms/Divider/Divider';
|
|
4
|
-
import
|
|
6
|
+
import Icon from '../../atoms/Icon/Icon';
|
|
5
7
|
import { Heading, Text } from '../../atoms/Typography/Typography';
|
|
6
8
|
import FeatureRow, { FeatureRowProps } from '../../molecules/FeatureRow/FeatureRow';
|
|
9
|
+
import VehicleSupplement, { Supplement, SupplementValue } from '../../molecules/VehicleSupplement/VehicleSupplement';
|
|
7
10
|
import './CarBookingCard.css';
|
|
8
11
|
|
|
9
12
|
export type CarBookingCardSize = 'small' | 'large';
|
|
@@ -40,14 +43,18 @@ export interface CarBookingCardProps {
|
|
|
40
43
|
/** Supplement dropdown section */
|
|
41
44
|
/** Whether to render the supplement section (matches Figma variants Supplement=Yes/No/-) */
|
|
42
45
|
showSupplement?: boolean;
|
|
43
|
-
/** Optional message shown
|
|
46
|
+
/** Optional message shown next to the "Supplement" label (Figma: "Supplement is not available for this vehicle.") */
|
|
44
47
|
supplementMessage?: string;
|
|
48
|
+
/** State variant for the supplement message: 'error' applies danger/red styling, 'default' uses normal text color */
|
|
49
|
+
supplementMessageState?: 'default' | 'error';
|
|
45
50
|
supplementLabel?: string;
|
|
46
51
|
supplementPlaceholder?: string;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
/** Array of available supplements */
|
|
53
|
+
supplements?: Supplement[];
|
|
54
|
+
/** Array of transfers for supplement selection */
|
|
55
|
+
transfers?: TransferDocket[];
|
|
56
|
+
/** Handler when supplements are selected */
|
|
57
|
+
onSupplementChange?: (values: SupplementValue[]) => void;
|
|
51
58
|
|
|
52
59
|
/** Footer price */
|
|
53
60
|
totalPrice: string;
|
|
@@ -56,7 +63,7 @@ export interface CarBookingCardProps {
|
|
|
56
63
|
/** Footer CTA */
|
|
57
64
|
ctaLabel: string;
|
|
58
65
|
ctaButtonProps?: Omit<ButtonProps, 'children'>;
|
|
59
|
-
onCtaClick?:
|
|
66
|
+
onCtaClick?: (event: React.MouseEvent<HTMLButtonElement>, supplements?: SupplementValue[]) => void;
|
|
60
67
|
|
|
61
68
|
/** Readonly mode - disables interactions and shows values as text */
|
|
62
69
|
readonly?: boolean;
|
|
@@ -76,12 +83,12 @@ const CarBookingCard: React.FC<CarBookingCardProps> = ({
|
|
|
76
83
|
priceRows = [],
|
|
77
84
|
showSupplement,
|
|
78
85
|
supplementMessage,
|
|
86
|
+
supplementMessageState = 'error',
|
|
79
87
|
supplementLabel = 'Supplement',
|
|
80
88
|
supplementPlaceholder = 'Select a supplement',
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
onSupplementSelect,
|
|
89
|
+
supplements = [],
|
|
90
|
+
transfers = [],
|
|
91
|
+
onSupplementChange,
|
|
85
92
|
totalPrice,
|
|
86
93
|
totalPriceLabel = 'Total price',
|
|
87
94
|
ctaLabel,
|
|
@@ -92,16 +99,94 @@ const CarBookingCard: React.FC<CarBookingCardProps> = ({
|
|
|
92
99
|
}) => {
|
|
93
100
|
const [isSelected, setIsSelected] = useState(state === 'selected');
|
|
94
101
|
const [isHovered, setIsHovered] = useState(false);
|
|
102
|
+
const [isSupplementOpen, setIsSupplementOpen] = useState(false);
|
|
103
|
+
const [selectedSupplements, setSelectedSupplements] = useState<SupplementValue[]>([]);
|
|
104
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
105
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
95
106
|
|
|
96
107
|
const resolvedShowSupplement =
|
|
97
|
-
showSupplement ?? Boolean(
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
showSupplement ?? Boolean(supplements.length > 0 && transfers.length > 0);
|
|
109
|
+
|
|
110
|
+
const hasSupplementsSelected = selectedSupplements.some((s) => s.value > 0);
|
|
111
|
+
|
|
112
|
+
// Close dropdown when clicking outside
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
115
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
116
|
+
setIsSupplementOpen(false);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (isSupplementOpen) {
|
|
121
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return () => {
|
|
125
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
126
|
+
};
|
|
127
|
+
}, [isSupplementOpen]);
|
|
128
|
+
|
|
129
|
+
// Auto-scroll to panel when opened
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (isSupplementOpen && panelRef.current) {
|
|
132
|
+
// Small delay to ensure the panel is rendered
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
panelRef.current?.scrollIntoView({
|
|
135
|
+
behavior: 'smooth',
|
|
136
|
+
block: 'nearest',
|
|
137
|
+
});
|
|
138
|
+
}, 100);
|
|
139
|
+
}
|
|
140
|
+
}, [isSupplementOpen]);
|
|
141
|
+
|
|
142
|
+
const getSupplementSummary = (): string[] => {
|
|
143
|
+
const summary: string[] = [];
|
|
144
|
+
const grouped = selectedSupplements.reduce((acc, item) => {
|
|
145
|
+
if (item.value > 0) {
|
|
146
|
+
if (!acc[item.supplementName]) {
|
|
147
|
+
acc[item.supplementName] = 0;
|
|
148
|
+
}
|
|
149
|
+
acc[item.supplementName] += item.value;
|
|
150
|
+
}
|
|
151
|
+
return acc;
|
|
152
|
+
}, {} as Record<string, number>);
|
|
153
|
+
|
|
154
|
+
Object.entries(grouped).forEach(([name, count]) => {
|
|
155
|
+
summary.push(`${name} x${count}`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return summary;
|
|
159
|
+
};
|
|
100
160
|
|
|
101
|
-
|
|
161
|
+
const handleSupplementDone = (values: SupplementValue[]) => {
|
|
162
|
+
setSelectedSupplements(values);
|
|
163
|
+
setIsSupplementOpen(false);
|
|
164
|
+
onSupplementChange?.(values);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleSupplementClear = () => {
|
|
168
|
+
setIsSupplementOpen(false);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const handleSupplementToggle = () => {
|
|
172
|
+
if (!supplementMessage) {
|
|
173
|
+
setIsSupplementOpen(!isSupplementOpen);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const handleRemoveSupplement = (supplementName: string) => {
|
|
178
|
+
const updatedValues = selectedSupplements.map((s) =>
|
|
179
|
+
s.supplementName === supplementName ? { ...s, value: 0 } : s
|
|
180
|
+
);
|
|
181
|
+
setSelectedSupplements(updatedValues);
|
|
182
|
+
onSupplementChange?.(updatedValues);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Handle CTA click: toggle between "Add to quote" and "Selected" and pass supplement data
|
|
102
186
|
const handleCtaClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
103
187
|
setIsSelected(!isSelected);
|
|
104
|
-
|
|
188
|
+
// Pass the selected supplements to the parent component
|
|
189
|
+
onCtaClick?.(e, selectedSupplements.length > 0 ? selectedSupplements : undefined);
|
|
105
190
|
};
|
|
106
191
|
|
|
107
192
|
// Determine button state based on selection and hover
|
|
@@ -206,31 +291,64 @@ const CarBookingCard: React.FC<CarBookingCardProps> = ({
|
|
|
206
291
|
size="sm"
|
|
207
292
|
leading="5"
|
|
208
293
|
variant="regular"
|
|
209
|
-
className=
|
|
294
|
+
className={`car-booking-card__supplement-message ${supplementMessageState === 'error' ? 'car-booking-card__supplement-message--error' : ''}`.trim()}
|
|
210
295
|
>
|
|
211
296
|
{supplementMessage}
|
|
212
297
|
</Text>
|
|
213
298
|
)}
|
|
214
299
|
</div>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
300
|
+
|
|
301
|
+
<div className="car-booking-card__supplement-dropdown" ref={dropdownRef}>
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
className={`car-booking-card__supplement-trigger ${hasSupplementsSelected ? 'car-booking-card__supplement-trigger--has-value' : ''} ${supplementMessage ? 'car-booking-card__supplement-trigger--disabled' : ''}`}
|
|
305
|
+
onClick={handleSupplementToggle}
|
|
306
|
+
disabled={Boolean(supplementMessage)}
|
|
222
307
|
>
|
|
223
|
-
{
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
308
|
+
{hasSupplementsSelected ? (
|
|
309
|
+
<div className="car-booking-card__supplement-chips">
|
|
310
|
+
{getSupplementSummary().map((summary, idx) => {
|
|
311
|
+
const [name] = summary.split(' x');
|
|
312
|
+
return (
|
|
313
|
+
<Chip
|
|
314
|
+
key={idx}
|
|
315
|
+
label={summary}
|
|
316
|
+
size="sm"
|
|
317
|
+
color="accent"
|
|
318
|
+
trailingIcon="close"
|
|
319
|
+
onClick={(e) => {
|
|
320
|
+
e.stopPropagation();
|
|
321
|
+
handleRemoveSupplement(name);
|
|
322
|
+
}}
|
|
323
|
+
/>
|
|
324
|
+
);
|
|
325
|
+
})}
|
|
326
|
+
</div>
|
|
327
|
+
) : (
|
|
328
|
+
<span className="car-booking-card__supplement-placeholder">
|
|
329
|
+
{supplementPlaceholder}
|
|
330
|
+
</span>
|
|
331
|
+
)}
|
|
332
|
+
<Icon
|
|
333
|
+
name="chevron-down"
|
|
334
|
+
size="sm"
|
|
335
|
+
className={`car-booking-card__supplement-icon ${isSupplementOpen ? 'car-booking-card__supplement-icon--open' : ''}`}
|
|
336
|
+
/>
|
|
337
|
+
</button>
|
|
338
|
+
|
|
339
|
+
{isSupplementOpen && (
|
|
340
|
+
<div className="car-booking-card__supplement-panel" ref={panelRef}>
|
|
341
|
+
<VehicleSupplement
|
|
342
|
+
supplements={supplements}
|
|
343
|
+
transfer={transfers}
|
|
344
|
+
initialValues={selectedSupplements}
|
|
345
|
+
onChange={() => {}}
|
|
346
|
+
onDone={handleSupplementDone}
|
|
347
|
+
onClear={handleSupplementClear}
|
|
348
|
+
/>
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
</div>
|
|
234
352
|
</section>
|
|
235
353
|
)}
|
|
236
354
|
|