@ticketboothapp/booking 1.2.190 → 1.2.191
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 +3 -1
- package/src/components/BackgroundVideo.tsx +6 -3
- package/src/components/booking/AddOnsSection.module.css +6 -2
- package/src/components/booking/AddOnsSection.tsx +4 -4
- package/src/components/booking/AdminChangeReceiptComparison.module.css +16 -0
- package/src/components/booking/AdminChangeReceiptComparison.tsx +14 -4
- package/src/components/booking/BookingDialog.module.css +43 -0
- package/src/components/booking/BookingFlowCollage.tsx +43 -16
- package/src/components/booking/Calendar.module.css +8 -3
- package/src/components/booking/ChangeBookingDialog.tsx +116 -55
- package/src/components/booking/ChangeBookingFlow.tsx +1 -0
- package/src/components/booking/CheckoutForm.module.css +7 -0
- package/src/components/booking/CollapsibleAddOnItem.module.css +3 -2
- package/src/components/booking/MealDrinkAddOnSelector.tsx +4 -4
- package/src/components/booking/PickupLocationDialog.tsx +19 -23
- package/src/components/booking/PickupLocationSelector.tsx +20 -25
- package/src/components/booking/PriceSummary.module.css +19 -0
- package/src/components/booking/PriceSummary.tsx +10 -7
- package/src/components/booking/TourDescription.module.css +6 -1
- package/src/components/booking/TourDescription.tsx +25 -16
- package/src/components/booking/change-booking-compare.module.css +154 -0
- package/src/components/booking/change-booking-compare.tsx +141 -1
- package/src/constants/images.ts +1 -1
- package/src/index.ts +11 -0
- package/src/lib/booking/pickup-location-disclaimer.ts +67 -0
- package/src/lib/booking-api.ts +1 -0
- package/src/strings/en.json +140 -68
- package/src/strings/es.json +181 -68
- package/src/strings/fr.json +181 -68
- package/test/pickup-location-disclaimer.test.ts +44 -0
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
display: flex;
|
|
18
18
|
flex-direction: column;
|
|
19
19
|
gap: 1rem;
|
|
20
|
+
min-width: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.contactSection > * {
|
|
24
|
+
min-width: 0;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
.contactGrid {
|
|
@@ -170,10 +175,12 @@
|
|
|
170
175
|
|
|
171
176
|
.contactStaticValue {
|
|
172
177
|
margin: 0;
|
|
178
|
+
max-width: 100%;
|
|
173
179
|
font-size: 1rem;
|
|
174
180
|
line-height: 1.4;
|
|
175
181
|
color: var(--booking-stone-900, #1c1917);
|
|
176
182
|
font-weight: 500;
|
|
183
|
+
overflow-wrap: anywhere;
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
.pickupSection {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
.card {
|
|
2
2
|
overflow: hidden;
|
|
3
|
-
|
|
3
|
+
/* The scoped booking preflight can be emitted after CSS modules in local dev. */
|
|
4
|
+
border: 1px solid var(--booking-stone-200, #e7e5e4) !important;
|
|
4
5
|
border-radius: 0.75rem;
|
|
5
6
|
background: #fff;
|
|
6
7
|
}
|
|
@@ -67,6 +68,6 @@
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
.content {
|
|
70
|
-
border-top: 1px solid var(--booking-stone-100, #f5f5f4);
|
|
71
|
+
border-top: 1px solid var(--booking-stone-100, #f5f5f4) !important;
|
|
71
72
|
padding: 0.75rem 0.875rem;
|
|
72
73
|
}
|
|
@@ -280,7 +280,7 @@ export function MealDrinkAddOnSelector({
|
|
|
280
280
|
updateSelections(next, drinks);
|
|
281
281
|
}}
|
|
282
282
|
disabled={qty <= 0 || !canDecrementTotal}
|
|
283
|
-
className=
|
|
283
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm`}
|
|
284
284
|
>
|
|
285
285
|
−
|
|
286
286
|
</button>
|
|
@@ -295,7 +295,7 @@ export function MealDrinkAddOnSelector({
|
|
|
295
295
|
};
|
|
296
296
|
updateSelections(next, drinks);
|
|
297
297
|
}}
|
|
298
|
-
className=
|
|
298
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 text-sm`}
|
|
299
299
|
>
|
|
300
300
|
+
|
|
301
301
|
</button>
|
|
@@ -338,7 +338,7 @@ export function MealDrinkAddOnSelector({
|
|
|
338
338
|
});
|
|
339
339
|
}}
|
|
340
340
|
disabled={qty <= 0}
|
|
341
|
-
className=
|
|
341
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm`}
|
|
342
342
|
>
|
|
343
343
|
−
|
|
344
344
|
</button>
|
|
@@ -357,7 +357,7 @@ export function MealDrinkAddOnSelector({
|
|
|
357
357
|
});
|
|
358
358
|
}}
|
|
359
359
|
disabled={otherQty <= 0}
|
|
360
|
-
className=
|
|
360
|
+
className={`${styles.quantityButton} h-8 w-8 rounded-full bg-white text-stone-600 hover:bg-stone-50 disabled:opacity-50 disabled:cursor-not-allowed text-sm`}
|
|
361
361
|
>
|
|
362
362
|
+
|
|
363
363
|
</button>
|
|
@@ -10,12 +10,9 @@ import {
|
|
|
10
10
|
} from '../../lib/booking-api';
|
|
11
11
|
import { PickupLocationSelector } from './PickupLocationSelector';
|
|
12
12
|
import { useTranslations } from '../../lib/booking/i18n';
|
|
13
|
+
import { getPickupLocationDisclaimer } from '../../lib/booking/pickup-location-disclaimer';
|
|
13
14
|
import styles from './PickupLocationDialog.module.css';
|
|
14
15
|
|
|
15
|
-
const SAMSON_MALL_PICKUP_LOCATION_ID = 'loc_afkvmlNRwRqZ';
|
|
16
|
-
const SAMSON_MALL_PARKING_DISCLAIMER =
|
|
17
|
-
'No public parking at this pickup location. This pickup location is meant to serve guests staying at the HI Lake Louise Alpine Hostel, Lake Louise Inn, or Lake Louise Campground and can walk to Samson Mall for pickup due to parking restrictions in the town of Lake Louise.';
|
|
18
|
-
|
|
19
16
|
function findProductWithPickupLocations(
|
|
20
17
|
products: Product[],
|
|
21
18
|
bookingProductId: string
|
|
@@ -31,13 +28,6 @@ function findProductWithPickupLocations(
|
|
|
31
28
|
return null;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
function hasSamsonMallParkingDisclaimer(location: { id?: string | null; name?: string | null }): boolean {
|
|
35
|
-
return (
|
|
36
|
-
location.id === SAMSON_MALL_PICKUP_LOCATION_ID ||
|
|
37
|
-
location.name?.trim().toLowerCase() === 'samson mall'
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
31
|
/** Itinerary rows shown on manage-booking (may include optional label/time from API). */
|
|
42
32
|
export interface PickupDialogItineraryStep {
|
|
43
33
|
label?: string | null;
|
|
@@ -228,20 +218,26 @@ export function PickupLocationDialog({
|
|
|
228
218
|
lastName,
|
|
229
219
|
]);
|
|
230
220
|
|
|
221
|
+
const currentPickupLocation = booking.pickupLocationId
|
|
222
|
+
? pickupLocations.find((l) => l.id === booking.pickupLocationId) ?? null
|
|
223
|
+
: null;
|
|
231
224
|
const currentPickupName =
|
|
232
225
|
booking.travelerHotel ||
|
|
233
226
|
(booking.pickupLocationId
|
|
234
|
-
?
|
|
227
|
+
? currentPickupLocation?.name ?? booking.pickupLocationId
|
|
235
228
|
: null);
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
const currentPickupDisclaimer = getPickupLocationDisclaimer(
|
|
230
|
+
currentPickupLocation ?? {
|
|
231
|
+
id: booking.pickupLocationId,
|
|
232
|
+
name: currentPickupName,
|
|
233
|
+
}
|
|
234
|
+
);
|
|
240
235
|
const selectedPickupLocation = selectedLocationId
|
|
241
236
|
? pickupLocations.find((l) => l.id === selectedLocationId) ?? null
|
|
242
237
|
: null;
|
|
243
|
-
const
|
|
244
|
-
|
|
238
|
+
const selectedPickupDisclaimer = selectedPickupLocation
|
|
239
|
+
? getPickupLocationDisclaimer(selectedPickupLocation)
|
|
240
|
+
: null;
|
|
245
241
|
|
|
246
242
|
const itineraryDisplay = booking.itineraryDisplay ?? [];
|
|
247
243
|
const pickupOrDropOffSteps = itineraryDisplay.filter(
|
|
@@ -332,9 +328,9 @@ export function PickupLocationDialog({
|
|
|
332
328
|
{getDropoffTimeFromItinerary(itineraryDisplay)}
|
|
333
329
|
</p>
|
|
334
330
|
)}
|
|
335
|
-
{
|
|
331
|
+
{currentPickupDisclaimer && (
|
|
336
332
|
<p className={styles.parkingNotice} role="note">
|
|
337
|
-
{
|
|
333
|
+
{currentPickupDisclaimer.message}
|
|
338
334
|
</p>
|
|
339
335
|
)}
|
|
340
336
|
</div>
|
|
@@ -366,10 +362,10 @@ export function PickupLocationDialog({
|
|
|
366
362
|
/>
|
|
367
363
|
</div>
|
|
368
364
|
|
|
369
|
-
{
|
|
365
|
+
{selectedPickupDisclaimer && (
|
|
370
366
|
<div className={styles.selectedParkingNotice} role="note">
|
|
371
|
-
<strong>
|
|
372
|
-
<span>{
|
|
367
|
+
<strong>{selectedPickupDisclaimer.title}</strong>
|
|
368
|
+
<span>{selectedPickupDisclaimer.message}</span>
|
|
373
369
|
</div>
|
|
374
370
|
)}
|
|
375
371
|
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from '../../lib/booking/map-utils';
|
|
34
34
|
import type { SearchedLocation, NearbyLocation } from '../../lib/booking/pickup-location-types';
|
|
35
35
|
import { useTranslations } from '../../lib/booking/i18n';
|
|
36
|
+
import { getPickupLocationDisclaimer } from '../../lib/booking/pickup-location-disclaimer';
|
|
36
37
|
import { useBookingApp } from '../../contexts/BookingAppContext';
|
|
37
38
|
import styles from './PickupLocationSelector.module.css';
|
|
38
39
|
|
|
@@ -77,9 +78,6 @@ const SUGGESTION_HIDE_DELAY = 200; // ms
|
|
|
77
78
|
const DEFAULT_MAP_ZOOM = 10;
|
|
78
79
|
const SEARCHED_LOCATION_ZOOM = 14;
|
|
79
80
|
const MAX_ZOOM = 15;
|
|
80
|
-
const SAMSON_MALL_PICKUP_LOCATION_ID = 'loc_afkvmlNRwRqZ';
|
|
81
|
-
const SAMSON_MALL_PARKING_DISCLAIMER =
|
|
82
|
-
'No public parking at this pickup location. This pickup location is meant to serve guests staying at the HI Lake Louise Alpine Hostel, Lake Louise Inn, or Lake Louise Campground and can walk to Samson Mall for pickup due to parking restrictions in the town of Lake Louise.';
|
|
83
81
|
|
|
84
82
|
// Filter definitions
|
|
85
83
|
const FILTERS = [
|
|
@@ -114,8 +112,21 @@ function hasFreeParking(location: PickupLocation): boolean {
|
|
|
114
112
|
return location.name.toLowerCase().includes('free parking');
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
function
|
|
118
|
-
|
|
115
|
+
function PickupLocationDisclaimerNote({
|
|
116
|
+
location,
|
|
117
|
+
className,
|
|
118
|
+
}: {
|
|
119
|
+
location: Pick<PickupLocation, 'id' | 'name' | 'disclaimer'>;
|
|
120
|
+
className: string;
|
|
121
|
+
}) {
|
|
122
|
+
const disclaimer = getPickupLocationDisclaimer(location);
|
|
123
|
+
if (!disclaimer) return null;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<p className={className} role="note">
|
|
127
|
+
{disclaimer.message}
|
|
128
|
+
</p>
|
|
129
|
+
);
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
// Filter locations based on selected filters (AND logic)
|
|
@@ -1213,11 +1224,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1213
1224
|
)}
|
|
1214
1225
|
</div>
|
|
1215
1226
|
<p className="text-xs text-stone-600 truncate">{location.address}</p>
|
|
1216
|
-
{
|
|
1217
|
-
<p className={styles.parkingDisclaimer} role="note">
|
|
1218
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1219
|
-
</p>
|
|
1220
|
-
)}
|
|
1227
|
+
<PickupLocationDisclaimerNote location={location} className={styles.parkingDisclaimer} />
|
|
1221
1228
|
{isNearby && searchedLocation && !exactMatchLocationId && !cityFilter && (
|
|
1222
1229
|
<div className="flex items-center gap-2 mt-1 text-xs text-stone-500">
|
|
1223
1230
|
<span>📍 {formatDistance((location as NearbyLocation).distance, useImperial)}</span>
|
|
@@ -1318,11 +1325,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1318
1325
|
<p className="text-sm text-stone-600 mb-2">
|
|
1319
1326
|
{location.address}
|
|
1320
1327
|
</p>
|
|
1321
|
-
{
|
|
1322
|
-
<p className={styles.mapParkingDisclaimer}>
|
|
1323
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1324
|
-
</p>
|
|
1325
|
-
)}
|
|
1328
|
+
<PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
|
|
1326
1329
|
<div className="text-xs text-stone-500 space-y-1">
|
|
1327
1330
|
<p>
|
|
1328
1331
|
📍 {formatDistance(location.distance, useImperial)} {t('pickup.away')}
|
|
@@ -1376,11 +1379,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1376
1379
|
<p className="text-sm text-stone-600 mb-2">
|
|
1377
1380
|
{location.address}
|
|
1378
1381
|
</p>
|
|
1379
|
-
{
|
|
1380
|
-
<p className={styles.mapParkingDisclaimer}>
|
|
1381
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1382
|
-
</p>
|
|
1383
|
-
)}
|
|
1382
|
+
<PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
|
|
1384
1383
|
{location.notes && (
|
|
1385
1384
|
<p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded p-2 mb-2">
|
|
1386
1385
|
{location.notes}
|
|
@@ -1438,11 +1437,7 @@ function PickupLocationSelectorWithMap(props: PickupLocationSelectorProps) {
|
|
|
1438
1437
|
<p className="text-sm text-stone-600 mb-2">
|
|
1439
1438
|
{location.address}
|
|
1440
1439
|
</p>
|
|
1441
|
-
{
|
|
1442
|
-
<p className={styles.mapParkingDisclaimer}>
|
|
1443
|
-
{SAMSON_MALL_PARKING_DISCLAIMER}
|
|
1444
|
-
</p>
|
|
1445
|
-
)}
|
|
1440
|
+
<PickupLocationDisclaimerNote location={location} className={styles.mapParkingDisclaimer} />
|
|
1446
1441
|
{location.notes && (
|
|
1447
1442
|
<p className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded p-2 mb-2">
|
|
1448
1443
|
{location.notes}
|
|
@@ -5,3 +5,22 @@
|
|
|
5
5
|
.ruleAbove {
|
|
6
6
|
border-top: 1px solid var(--booking-stone-200, #e7e5e4);
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
@media (max-width: 639px) {
|
|
10
|
+
.compactMobileTotal .totalRow {
|
|
11
|
+
align-items: flex-start;
|
|
12
|
+
font-size: 1rem !important;
|
|
13
|
+
line-height: 1.3 !important;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.compactMobileTotal .totalLabel {
|
|
17
|
+
overflow: visible !important;
|
|
18
|
+
white-space: normal !important;
|
|
19
|
+
text-overflow: clip !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.compactMobileTotal .totalAmount {
|
|
23
|
+
font-size: 1rem !important;
|
|
24
|
+
line-height: 1.3 !important;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -84,6 +84,8 @@ export interface PriceSummaryProps {
|
|
|
84
84
|
totalLabel?: string;
|
|
85
85
|
/** Prefix a positive total with + when the row represents a signed change rather than an absolute total. */
|
|
86
86
|
showPositiveTotalSign?: boolean;
|
|
87
|
+
/** Keep the total row compact and allow its label to wrap on narrow mobile screens. */
|
|
88
|
+
compactMobileTotal?: boolean;
|
|
87
89
|
/** Render after the total row (e.g. provider price delta + keep-original-price) */
|
|
88
90
|
extraAfterTotal?: ReactNode;
|
|
89
91
|
/** Optional map of editable line input values by line key. */
|
|
@@ -188,6 +190,7 @@ export function PriceSummary({
|
|
|
188
190
|
hideSubtotal = false,
|
|
189
191
|
totalLabel,
|
|
190
192
|
showPositiveTotalSign = false,
|
|
193
|
+
compactMobileTotal = false,
|
|
191
194
|
extraAfterTotal,
|
|
192
195
|
lineAmountInputs,
|
|
193
196
|
lineLabelInputs,
|
|
@@ -218,7 +221,7 @@ export function PriceSummary({
|
|
|
218
221
|
!embedSubtotalBeforeTaxInLines && extraBeforeSubtotal ? extraBeforeSubtotal : null;
|
|
219
222
|
|
|
220
223
|
return (
|
|
221
|
-
<div className={`space-y-2 min-w-0 ${className}`}>
|
|
224
|
+
<div className={`space-y-2 min-w-0 ${compactMobileTotal ? styles.compactMobileTotal : ''} ${className}`}>
|
|
222
225
|
{lines.map((row, index) => {
|
|
223
226
|
const isBeforeSubtotalBoundary = firstTaxLineIndex < 0 || index < firstTaxLineIndex;
|
|
224
227
|
if (row.kind === 'ticket') {
|
|
@@ -375,11 +378,11 @@ export function PriceSummary({
|
|
|
375
378
|
<div className="space-y-0">
|
|
376
379
|
{depositMode ? (
|
|
377
380
|
<>
|
|
378
|
-
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
|
|
379
|
-
<span className=
|
|
381
|
+
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove} ${styles.totalRow}`}>
|
|
382
|
+
<span className={`font-semibold text-stone-900 min-w-0 truncate ${styles.totalLabel}`}>
|
|
380
383
|
{t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total'}
|
|
381
384
|
</span>
|
|
382
|
-
<span className=
|
|
385
|
+
<span className={`flex-shrink-0 whitespace-nowrap font-semibold text-stone-900 ${styles.totalAmount}`}>
|
|
383
386
|
{formatCurrencyAmount(depositMode.fullTotalAmount, currency, locale)}
|
|
384
387
|
</span>
|
|
385
388
|
</div>
|
|
@@ -399,12 +402,12 @@ export function PriceSummary({
|
|
|
399
402
|
)}
|
|
400
403
|
</>
|
|
401
404
|
) : (
|
|
402
|
-
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove}`}>
|
|
403
|
-
<span className=
|
|
405
|
+
<div className={`flex justify-between gap-3 pt-2 min-w-0 ${totalSize} ${styles.ruleAbove} ${styles.totalRow}`}>
|
|
406
|
+
<span className={`font-semibold text-stone-900 min-w-0 truncate ${styles.totalLabel}`}>
|
|
404
407
|
{totalLabel ??
|
|
405
408
|
(t('common.total') && t('common.total') !== 'common.total' ? t('common.total') : 'Total')}
|
|
406
409
|
</span>
|
|
407
|
-
<span className=
|
|
410
|
+
<span className={`flex-shrink-0 whitespace-nowrap font-semibold text-stone-900 ${styles.totalAmount}`}>
|
|
408
411
|
{showPositiveTotalSign && total > 0.005 ? '+' : ''}
|
|
409
412
|
{formatCurrencyAmount(total, currency, locale)}
|
|
410
413
|
</span>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
border-radius: 0.5rem;
|
|
18
18
|
border: none;
|
|
19
19
|
cursor: pointer;
|
|
20
|
-
font-family: '
|
|
20
|
+
font-family: 'Figtree', sans-serif !important;
|
|
21
21
|
font-size: 1.125rem;
|
|
22
22
|
font-weight: 700;
|
|
23
23
|
text-transform: lowercase;
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
transition: opacity 0.2s;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
.root .mainToggleStatic {
|
|
30
|
+
position: relative;
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
.root .mainToggle:hover {
|
|
30
34
|
opacity: 0.9;
|
|
31
35
|
}
|
|
@@ -168,6 +172,7 @@
|
|
|
168
172
|
background: none;
|
|
169
173
|
border: none;
|
|
170
174
|
cursor: pointer;
|
|
175
|
+
font-family: 'Figtree', sans-serif !important;
|
|
171
176
|
font-size: 0.9375rem;
|
|
172
177
|
font-weight: 700 !important;
|
|
173
178
|
color: var(--accent-orange) !important;
|
|
@@ -41,6 +41,10 @@ export interface TourDescriptionProps {
|
|
|
41
41
|
sections?: TourDescriptionSection[];
|
|
42
42
|
/** Start expanded (e.g. when in partial/pre-launch state) */
|
|
43
43
|
defaultExpanded?: boolean;
|
|
44
|
+
/** Keep the main toggle visible while scrolling inside booking dialogs. */
|
|
45
|
+
stickyToggle?: boolean;
|
|
46
|
+
/** Show the description content without a collapse control. */
|
|
47
|
+
alwaysExpanded?: boolean;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/** Items starting with • - * are bullet list; otherwise join as HTML (multi-line format) */
|
|
@@ -123,6 +127,8 @@ export function TourDescription({
|
|
|
123
127
|
review: reviewProp,
|
|
124
128
|
sections: sectionsProp,
|
|
125
129
|
defaultExpanded = false,
|
|
130
|
+
stickyToggle = true,
|
|
131
|
+
alwaysExpanded = false,
|
|
126
132
|
}: TourDescriptionProps) {
|
|
127
133
|
const { catalog, slots } = useBookingHost();
|
|
128
134
|
const PlusIcon = slots.PlusIcon;
|
|
@@ -154,6 +160,7 @@ export function TourDescription({
|
|
|
154
160
|
const displayReview = review ?? null;
|
|
155
161
|
const displaySections =
|
|
156
162
|
sections.length > 0 ? sections : [];
|
|
163
|
+
const contentIsExpanded = alwaysExpanded || isExpanded;
|
|
157
164
|
|
|
158
165
|
/** Group paragraphs: consecutive bullet items become a single <ul> block */
|
|
159
166
|
const paragraphBlocks = useMemo(() => {
|
|
@@ -178,23 +185,25 @@ export function TourDescription({
|
|
|
178
185
|
|
|
179
186
|
return (
|
|
180
187
|
<div className={styles.root}>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
{isExpanded ?
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
{!alwaysExpanded && (
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
className={`${styles.mainToggle} ${stickyToggle ? '' : styles.mainToggleStatic}`}
|
|
192
|
+
onClick={() => setIsExpanded((e) => !e)}
|
|
193
|
+
aria-expanded={isExpanded}
|
|
194
|
+
>
|
|
195
|
+
<span>{toggleLabel ?? t('booking.seeFullTourDescription')}</span>
|
|
196
|
+
<span className={`${styles.toggleIcon} ${isExpanded ? styles.toggleIconExpanded : ''}`} aria-hidden>
|
|
197
|
+
{isExpanded ? (
|
|
198
|
+
<MinusIcon />
|
|
199
|
+
) : (
|
|
200
|
+
<PlusIcon />
|
|
201
|
+
)}
|
|
202
|
+
</span>
|
|
203
|
+
</button>
|
|
204
|
+
)}
|
|
196
205
|
|
|
197
|
-
<div className={`${styles.contentWrapper} ${
|
|
206
|
+
<div className={`${styles.contentWrapper} ${contentIsExpanded ? styles.contentExpanded : ''}`}>
|
|
198
207
|
<div className={styles.content}>
|
|
199
208
|
{paragraphBlocks.map((block, i) =>
|
|
200
209
|
block.type === 'paragraph' ? (
|
|
@@ -95,3 +95,157 @@
|
|
|
95
95
|
line-height: 1.2;
|
|
96
96
|
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
.mobileDigest {
|
|
100
|
+
display: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media (max-width: 767px) {
|
|
104
|
+
.kickerMobileHidden {
|
|
105
|
+
display: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.mobileDigest {
|
|
109
|
+
display: block;
|
|
110
|
+
padding: 0 0.25rem;
|
|
111
|
+
font-family: 'Figtree', var(--booking-font-sans, ui-sans-serif), sans-serif;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.mobileDigest .mobileDigestToggle {
|
|
115
|
+
display: block;
|
|
116
|
+
width: 100%;
|
|
117
|
+
min-height: 2.75rem;
|
|
118
|
+
margin: 0;
|
|
119
|
+
padding: 0.625rem 0.75rem;
|
|
120
|
+
border: 0;
|
|
121
|
+
border-radius: 0.75rem;
|
|
122
|
+
background: transparent;
|
|
123
|
+
color: var(--primary-text, var(--booking-text, #1c1917));
|
|
124
|
+
font: inherit;
|
|
125
|
+
text-align: left;
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
appearance: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.mobileDigest .mobileDigestToggle:focus-visible {
|
|
131
|
+
outline: 2px solid var(--accent-orange, #ff4d00);
|
|
132
|
+
outline-offset: 2px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.mobileDigestExpanded .mobileDigestToggle {
|
|
136
|
+
border-radius: 0.75rem 0.75rem 0 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.mobileDigestHeading,
|
|
140
|
+
.mobileDigestAction,
|
|
141
|
+
.mobileDigestLine,
|
|
142
|
+
.mobileDigestTotalLine {
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: baseline;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.mobileDigestHeading {
|
|
148
|
+
justify-content: space-between;
|
|
149
|
+
gap: 0.75rem;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.mobileDigestKicker {
|
|
153
|
+
font-size: 0.625rem;
|
|
154
|
+
font-weight: 700;
|
|
155
|
+
letter-spacing: 0.07em;
|
|
156
|
+
line-height: 1.2;
|
|
157
|
+
text-transform: uppercase;
|
|
158
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.mobileDigestAction {
|
|
162
|
+
flex-shrink: 0;
|
|
163
|
+
gap: 0.125rem;
|
|
164
|
+
font-size: 0.75rem;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
line-height: 1.2;
|
|
167
|
+
color: var(--accent-orange, #ff4d00);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.mobileDigestChevron {
|
|
171
|
+
flex-shrink: 0;
|
|
172
|
+
align-self: center;
|
|
173
|
+
transition: transform 160ms ease;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.mobileDigestChevronExpanded {
|
|
177
|
+
transform: rotate(180deg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.mobileDigestChanges {
|
|
181
|
+
display: grid;
|
|
182
|
+
gap: 0.125rem;
|
|
183
|
+
margin-top: 0.375rem;
|
|
184
|
+
font-size: 0.8125rem;
|
|
185
|
+
line-height: 1.3;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.mobileDigestLine {
|
|
189
|
+
min-width: 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.mobileDigestArrow {
|
|
193
|
+
margin: 0 0.35rem;
|
|
194
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.mobileDigestNewValue {
|
|
198
|
+
font-weight: 700;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.mobileDigestMuted {
|
|
202
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.mobileDigestTotalLine {
|
|
206
|
+
justify-content: space-between;
|
|
207
|
+
gap: 0.75rem;
|
|
208
|
+
padding-top: 0.125rem;
|
|
209
|
+
font-variant-numeric: tabular-nums;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.deltaIncrease,
|
|
213
|
+
.deltaDecrease,
|
|
214
|
+
.deltaSame {
|
|
215
|
+
flex-shrink: 0;
|
|
216
|
+
font-weight: 700;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.deltaIncrease {
|
|
220
|
+
color: var(--accent-orange, #ff4d00);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.deltaDecrease {
|
|
224
|
+
color: var(--booking-emerald-700, #047857);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.deltaSame {
|
|
228
|
+
color: var(--grey-text, var(--booking-text-muted, #78716c));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.mobileDigestBaseline {
|
|
232
|
+
display: block;
|
|
233
|
+
margin-top: 0.375rem;
|
|
234
|
+
font-size: 0.8125rem;
|
|
235
|
+
font-weight: 500;
|
|
236
|
+
line-height: 1.3;
|
|
237
|
+
font-variant-numeric: tabular-nums;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.visuallyHidden {
|
|
241
|
+
position: absolute;
|
|
242
|
+
width: 1px;
|
|
243
|
+
height: 1px;
|
|
244
|
+
padding: 0;
|
|
245
|
+
margin: -1px;
|
|
246
|
+
overflow: hidden;
|
|
247
|
+
clip: rect(0, 0, 0, 0);
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
border: 0;
|
|
250
|
+
}
|
|
251
|
+
}
|