mautourco-components 0.2.46 → 0.2.47
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/molecules/AgeSelector/AgeSelector.d.ts +20 -0
- package/dist/components/molecules/AgeSelector/AgeSelector.js +84 -0
- package/dist/components/molecules/LocationDropdown/LocationDropdown.js +1 -1
- package/dist/components/organisms/PaxSelector/PaxSelector.d.ts +5 -0
- package/dist/components/organisms/PaxSelector/PaxSelector.js +100 -114
- package/dist/components/organisms/RoundTrip/RoundTrip.d.ts +2 -0
- package/dist/components/organisms/RoundTrip/RoundTrip.js +7 -7
- package/dist/components/organisms/SearchBarTransfer/SearchBarTransfer.d.ts +4 -0
- package/dist/components/organisms/SearchBarTransfer/SearchBarTransfer.js +19 -57
- package/dist/components/organisms/TransferLine/TransferLine.d.ts +9 -5
- package/dist/components/organisms/TransferLine/TransferLine.js +52 -35
- package/dist/index.d.ts +19 -16
- package/dist/index.js +3 -1
- package/dist/styles/components/molecule/age-selector.css +216 -0
- package/dist/styles/components/molecule/calendarInput.css +25 -6
- package/dist/styles/components/molecule/location-dropdown.css +16 -4
- package/dist/styles/components/organism/pax-selector.css +27 -189
- package/dist/styles/components/organism/transfer-line.css +40 -0
- package/dist/styles/mautourco.css +1 -0
- package/package.json +1 -1
- package/src/components/molecules/AgeSelector/AgeSelector.tsx +172 -0
- package/src/components/molecules/LocationDropdown/LocationDropdown.tsx +1 -1
- package/src/components/organisms/PaxSelector/PaxSelector.tsx +132 -208
- package/src/components/organisms/RoundTrip/RoundTrip.tsx +7 -0
- package/src/components/organisms/SearchBarTransfer/SearchBarTransfer.tsx +34 -54
- package/src/components/organisms/TransferLine/TransferLine.tsx +107 -85
- package/src/styles/components/molecule/age-selector.css +136 -0
- package/src/styles/components/molecule/calendarInput.css +12 -4
- package/src/styles/components/molecule/location-dropdown.css +9 -2
- package/src/styles/components/organism/pax-selector.css +25 -186
- package/src/styles/components/organism/transfer-line.css +31 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { scrollIntoViewOnOpen } from '../../../lib/utils';
|
|
3
3
|
import Icon from '../../atoms/Icon/Icon';
|
|
4
4
|
import { Text } from '../../atoms/Typography/Typography';
|
|
5
|
+
import AgeSelector from '../../molecules/AgeSelector/AgeSelector';
|
|
5
6
|
|
|
6
7
|
export type ClientType = 'Standard client' | 'VIP' | 'VVIP' | 'Honeymooners';
|
|
7
8
|
|
|
@@ -65,8 +66,12 @@ export interface PaxSelectorProps {
|
|
|
65
66
|
onRemoveRoom?: (roomId: string) => void;
|
|
66
67
|
/** Default pax data for single room mode (will trigger onChange on mount) */
|
|
67
68
|
defaultPaxData?: PaxData;
|
|
69
|
+
/** Whether the selector is disabled */
|
|
70
|
+
disabled?: boolean;
|
|
68
71
|
/** Whether to scroll to the input when the dropdown opens */
|
|
69
72
|
scrollOnOpen?: boolean;
|
|
73
|
+
/** Age range for child categories */
|
|
74
|
+
ageRange?: number[];
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
const DEFAULT_PAX_DATA: PaxData = {
|
|
@@ -94,178 +99,7 @@ export const DEFAULT_PAX_DATA_WITH_ADULTS: PaxData = {
|
|
|
94
99
|
const CLIENT_TYPES: ClientType[] = ['Standard client', 'VIP', 'VVIP', 'Honeymooners'];
|
|
95
100
|
|
|
96
101
|
// Age range for all child categories (teens, children, infants)
|
|
97
|
-
const CHILD_CATEGORY_AGES = Array.from({ length: 18 }, (_, i) => i + 1); // 1-18 years
|
|
98
|
-
|
|
99
|
-
interface AgeSelectorProps {
|
|
100
|
-
label: string;
|
|
101
|
-
value: number | undefined;
|
|
102
|
-
onChange: (age: number) => void;
|
|
103
|
-
ageRange: number[];
|
|
104
|
-
required?: boolean;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const AgeSelector: React.FC<AgeSelectorProps> = ({
|
|
108
|
-
label,
|
|
109
|
-
value,
|
|
110
|
-
onChange,
|
|
111
|
-
ageRange,
|
|
112
|
-
required,
|
|
113
|
-
}) => {
|
|
114
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
115
|
-
const [inputValue, setInputValue] = useState<string>(
|
|
116
|
-
value !== undefined ? value.toString() : ''
|
|
117
|
-
);
|
|
118
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
119
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
120
|
-
|
|
121
|
-
// Sync input value when prop value changes
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
setInputValue(value !== undefined ? value.toString() : '');
|
|
124
|
-
}, [value]);
|
|
125
|
-
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
128
|
-
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
129
|
-
setIsOpen(false);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
if (isOpen) {
|
|
134
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return () => {
|
|
138
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
139
|
-
};
|
|
140
|
-
}, [isOpen]);
|
|
141
|
-
|
|
142
|
-
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
143
|
-
const newValue = e.target.value;
|
|
144
|
-
|
|
145
|
-
// Only allow numeric input or empty string
|
|
146
|
-
if (newValue === '' || /^\d+$/.test(newValue)) {
|
|
147
|
-
setInputValue(newValue);
|
|
148
|
-
|
|
149
|
-
// Only update if it's a valid number within range
|
|
150
|
-
const numValue = parseInt(newValue, 10);
|
|
151
|
-
if (newValue === '') {
|
|
152
|
-
// Allow empty input - don't update onChange
|
|
153
|
-
return;
|
|
154
|
-
} else if (!isNaN(numValue) && ageRange.includes(numValue)) {
|
|
155
|
-
onChange(numValue);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const handleInputBlur = () => {
|
|
161
|
-
// Validate and set to valid value or clear if invalid
|
|
162
|
-
const numValue = parseInt(inputValue, 10);
|
|
163
|
-
if (inputValue === '') {
|
|
164
|
-
// Keep empty if user cleared it
|
|
165
|
-
return;
|
|
166
|
-
} else if (isNaN(numValue) || !ageRange.includes(numValue)) {
|
|
167
|
-
// Reset to current value if invalid
|
|
168
|
-
setInputValue(value !== undefined ? value.toString() : '');
|
|
169
|
-
} else {
|
|
170
|
-
// Ensure the input value matches the validated value
|
|
171
|
-
setInputValue(numValue.toString());
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
176
|
-
if (e.key === 'Enter') {
|
|
177
|
-
e.preventDefault();
|
|
178
|
-
inputRef.current?.blur();
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const handleSelect = (age: string) => {
|
|
183
|
-
const numAge = parseInt(age, 10);
|
|
184
|
-
onChange(numAge);
|
|
185
|
-
setIsOpen(false);
|
|
186
|
-
setInputValue(age);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const handleDropdownToggle = () => {
|
|
190
|
-
setIsOpen(!isOpen);
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const ageOptions = ageRange.map((age) => age.toString());
|
|
194
|
-
const displayValue = inputValue || undefined;
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<div className="pax-selector__age-selector" ref={containerRef}>
|
|
198
|
-
<Text size="sm" variant="regular" className="pax-selector__age-label">
|
|
199
|
-
{label}
|
|
200
|
-
{required && <span className="pax-selector__age-required"> *</span>}
|
|
201
|
-
</Text>
|
|
202
|
-
<div className="dropdown-container pax-selector__age-dropdown-container">
|
|
203
|
-
<div
|
|
204
|
-
className={`dropdown-input ${isOpen ? 'dropdown-input--open pax-selector__age-dropdown-input--open' : ''} ${value !== undefined ? 'dropdown-input--selected' : 'dropdown-input--default'} pax-selector__age-dropdown-input`}>
|
|
205
|
-
<input
|
|
206
|
-
ref={inputRef}
|
|
207
|
-
type="text"
|
|
208
|
-
inputMode="numeric"
|
|
209
|
-
className="dropdown-input__text pax-selector__age-input-text"
|
|
210
|
-
value={inputValue}
|
|
211
|
-
onChange={handleInputChange}
|
|
212
|
-
onBlur={handleInputBlur}
|
|
213
|
-
onKeyDown={handleInputKeyDown}
|
|
214
|
-
onFocus={() => setIsOpen(false)}
|
|
215
|
-
placeholder="--"
|
|
216
|
-
aria-label={`${label} age`}
|
|
217
|
-
style={{
|
|
218
|
-
background: 'transparent',
|
|
219
|
-
border: 'none',
|
|
220
|
-
outline: 'none',
|
|
221
|
-
width: '100%',
|
|
222
|
-
cursor: 'text',
|
|
223
|
-
}}
|
|
224
|
-
/>
|
|
225
|
-
<button
|
|
226
|
-
type="button"
|
|
227
|
-
className="pax-selector__age-dropdown-btn"
|
|
228
|
-
onClick={(e) => {
|
|
229
|
-
e.preventDefault();
|
|
230
|
-
e.stopPropagation();
|
|
231
|
-
handleDropdownToggle();
|
|
232
|
-
}}
|
|
233
|
-
aria-expanded={isOpen}
|
|
234
|
-
aria-haspopup="listbox"
|
|
235
|
-
aria-label="Open age dropdown"
|
|
236
|
-
style={{
|
|
237
|
-
background: 'transparent',
|
|
238
|
-
border: 'none',
|
|
239
|
-
cursor: 'pointer',
|
|
240
|
-
padding: 0,
|
|
241
|
-
display: 'flex',
|
|
242
|
-
alignItems: 'center',
|
|
243
|
-
}}>
|
|
244
|
-
<Icon
|
|
245
|
-
name="chevron-down"
|
|
246
|
-
size="sm"
|
|
247
|
-
className="dropdown-input__icon dropdown-input__icon--chevron"
|
|
248
|
-
/>
|
|
249
|
-
</button>
|
|
250
|
-
</div>
|
|
251
|
-
{isOpen && (
|
|
252
|
-
<div className="dropdown-menu" role="listbox">
|
|
253
|
-
{ageOptions.map((age) => (
|
|
254
|
-
<div
|
|
255
|
-
key={age}
|
|
256
|
-
className={`dropdown-option ${value?.toString() === age ? 'dropdown-option--selected' : ''}`}
|
|
257
|
-
onClick={() => handleSelect(age)}
|
|
258
|
-
role="option"
|
|
259
|
-
aria-selected={value?.toString() === age}>
|
|
260
|
-
{age}
|
|
261
|
-
</div>
|
|
262
|
-
))}
|
|
263
|
-
</div>
|
|
264
|
-
)}
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
);
|
|
268
|
-
};
|
|
102
|
+
export const CHILD_CATEGORY_AGES = Array.from({ length: 18 }, (_, i) => i + 1); // 1-18 years
|
|
269
103
|
|
|
270
104
|
interface StepperRowProps {
|
|
271
105
|
label: string;
|
|
@@ -410,6 +244,8 @@ interface RoomEditorProps {
|
|
|
410
244
|
maxInfants: number;
|
|
411
245
|
onChange: (room: RoomData) => void;
|
|
412
246
|
onRemove: () => void;
|
|
247
|
+
scrollToRef?: React.RefObject<HTMLDivElement | null>;
|
|
248
|
+
ageRange: number[];
|
|
413
249
|
}
|
|
414
250
|
|
|
415
251
|
const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
@@ -422,7 +258,12 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
422
258
|
maxInfants,
|
|
423
259
|
onChange,
|
|
424
260
|
onRemove,
|
|
261
|
+
scrollToRef,
|
|
262
|
+
ageRange,
|
|
425
263
|
}) => {
|
|
264
|
+
const roomAgesSectionRef = useRef<HTMLDivElement>(null);
|
|
265
|
+
const previousRoomCounts = useRef({ teens: 0, children: 0, infants: 0 });
|
|
266
|
+
|
|
426
267
|
const handleFieldChange = (
|
|
427
268
|
field: keyof PaxData,
|
|
428
269
|
value: number | ClientType | number[]
|
|
@@ -440,6 +281,38 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
440
281
|
handleFieldChange(category, ages);
|
|
441
282
|
};
|
|
442
283
|
|
|
284
|
+
// Scroll to age section when new age inputs are added in this room
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
const prev = previousRoomCounts.current;
|
|
287
|
+
const curr = {
|
|
288
|
+
teens: room.teens,
|
|
289
|
+
children: room.children,
|
|
290
|
+
infants: room.infants,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Check if any count increased
|
|
294
|
+
const hasIncrease =
|
|
295
|
+
curr.teens > prev.teens ||
|
|
296
|
+
curr.children > prev.children ||
|
|
297
|
+
curr.infants > prev.infants;
|
|
298
|
+
|
|
299
|
+
if (hasIncrease && roomAgesSectionRef.current) {
|
|
300
|
+
// Scroll to the age section after a short delay to ensure DOM is updated
|
|
301
|
+
setTimeout(() => {
|
|
302
|
+
if (roomAgesSectionRef.current) {
|
|
303
|
+
roomAgesSectionRef.current.scrollIntoView({
|
|
304
|
+
behavior: 'smooth',
|
|
305
|
+
block: 'nearest',
|
|
306
|
+
inline: 'nearest',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}, 100);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Update previous counts
|
|
313
|
+
previousRoomCounts.current = curr;
|
|
314
|
+
}, [room.teens, room.children, room.infants]);
|
|
315
|
+
|
|
443
316
|
// Generate age arrays based on counts
|
|
444
317
|
useEffect(() => {
|
|
445
318
|
const teenAges = room.teenAges || [];
|
|
@@ -491,7 +364,7 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
491
364
|
const infantAgeChunks = chunkAges(room.infantAges || [], 'Infant');
|
|
492
365
|
|
|
493
366
|
return (
|
|
494
|
-
<div className="pax-selector__room-container">
|
|
367
|
+
<div className="pax-selector__room-container" ref={scrollToRef}>
|
|
495
368
|
<div className="pax-selector__room-header">
|
|
496
369
|
<div className="pax-selector__room-title">
|
|
497
370
|
<Text
|
|
@@ -545,7 +418,7 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
545
418
|
|
|
546
419
|
{/* Age specification section */}
|
|
547
420
|
{(room.teens > 0 || room.children > 0 || room.infants > 0) && (
|
|
548
|
-
<div className="pax-selector__age-section">
|
|
421
|
+
<div className="pax-selector__age-section" ref={roomAgesSectionRef}>
|
|
549
422
|
<Text
|
|
550
423
|
size="base"
|
|
551
424
|
variant="bold"
|
|
@@ -558,7 +431,7 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
558
431
|
{/* Teen ages */}
|
|
559
432
|
{room.teens > 0 &&
|
|
560
433
|
teenAgeChunks.map((chunk, chunkIndex) => (
|
|
561
|
-
<
|
|
434
|
+
<Fragment key={`teen-chunk-${chunkIndex}`}>
|
|
562
435
|
{chunk.map((age, ageIndex) => {
|
|
563
436
|
const actualIndex = chunkIndex * 2 + ageIndex;
|
|
564
437
|
return (
|
|
@@ -569,20 +442,18 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
569
442
|
onChange={(selectedAge) =>
|
|
570
443
|
handleAgeChange('teenAges', actualIndex, selectedAge)
|
|
571
444
|
}
|
|
572
|
-
ageRange={
|
|
445
|
+
ageRange={ageRange}
|
|
573
446
|
required
|
|
574
447
|
/>
|
|
575
448
|
);
|
|
576
449
|
})}
|
|
577
|
-
</
|
|
450
|
+
</Fragment>
|
|
578
451
|
))}
|
|
579
452
|
|
|
580
453
|
{/* Child ages */}
|
|
581
454
|
{room.children > 0 &&
|
|
582
455
|
childAgeChunks.map((chunk, chunkIndex) => (
|
|
583
|
-
<
|
|
584
|
-
key={`child-chunk-${chunkIndex}`}
|
|
585
|
-
className="pax-selector__age-row">
|
|
456
|
+
<Fragment key={`child-chunk-${chunkIndex}`}>
|
|
586
457
|
{chunk.map((age, ageIndex) => {
|
|
587
458
|
const actualIndex = chunkIndex * 2 + ageIndex;
|
|
588
459
|
return (
|
|
@@ -593,20 +464,18 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
593
464
|
onChange={(selectedAge) =>
|
|
594
465
|
handleAgeChange('childAges', actualIndex, selectedAge)
|
|
595
466
|
}
|
|
596
|
-
ageRange={
|
|
467
|
+
ageRange={ageRange}
|
|
597
468
|
required
|
|
598
469
|
/>
|
|
599
470
|
);
|
|
600
471
|
})}
|
|
601
|
-
</
|
|
472
|
+
</Fragment>
|
|
602
473
|
))}
|
|
603
474
|
|
|
604
475
|
{/* Infant ages */}
|
|
605
476
|
{room.infants > 0 &&
|
|
606
477
|
infantAgeChunks.map((chunk, chunkIndex) => (
|
|
607
|
-
<
|
|
608
|
-
key={`infant-chunk-${chunkIndex}`}
|
|
609
|
-
className="pax-selector__age-row">
|
|
478
|
+
<Fragment key={`infant-chunk-${chunkIndex}`}>
|
|
610
479
|
{chunk.map((age, ageIndex) => {
|
|
611
480
|
const actualIndex = chunkIndex * 2 + ageIndex;
|
|
612
481
|
return (
|
|
@@ -617,12 +486,12 @@ const RoomEditor: React.FC<RoomEditorProps> = ({
|
|
|
617
486
|
onChange={(selectedAge) =>
|
|
618
487
|
handleAgeChange('infantAges', actualIndex, selectedAge)
|
|
619
488
|
}
|
|
620
|
-
ageRange={
|
|
489
|
+
ageRange={ageRange}
|
|
621
490
|
required
|
|
622
491
|
/>
|
|
623
492
|
);
|
|
624
493
|
})}
|
|
625
|
-
</
|
|
494
|
+
</Fragment>
|
|
626
495
|
))}
|
|
627
496
|
</div>
|
|
628
497
|
</div>
|
|
@@ -655,7 +524,9 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
655
524
|
onRoomsChange,
|
|
656
525
|
onRemoveRoom,
|
|
657
526
|
defaultPaxData = DEFAULT_PAX_DATA_WITH_ADULTS,
|
|
527
|
+
ageRange = CHILD_CATEGORY_AGES,
|
|
658
528
|
scrollOnOpen = false,
|
|
529
|
+
disabled = false,
|
|
659
530
|
}) => {
|
|
660
531
|
const [isOpen, setIsOpen] = useState(false);
|
|
661
532
|
const [internalData, setInternalData] = useState<PaxData>(
|
|
@@ -667,6 +538,9 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
667
538
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
668
539
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
669
540
|
const hasInitialized = useRef(false);
|
|
541
|
+
const lastRoomRef = useRef<HTMLDivElement>(null);
|
|
542
|
+
const agesSectionRef = useRef<HTMLDivElement>(null);
|
|
543
|
+
const previousCounts = useRef({ teens: 0, children: 0, infants: 0 });
|
|
670
544
|
|
|
671
545
|
// Sync internal data with external value prop
|
|
672
546
|
useEffect(() => {
|
|
@@ -765,6 +639,46 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
765
639
|
scrollIntoViewOnOpen(triggerRef, isOpen, scrollOnOpen);
|
|
766
640
|
}, [isOpen, scrollOnOpen]);
|
|
767
641
|
|
|
642
|
+
// Scroll to age section when new age inputs are added (single room mode)
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
if (!multipleRooms && isOpen) {
|
|
645
|
+
const prev = previousCounts.current;
|
|
646
|
+
const curr = {
|
|
647
|
+
teens: internalData.teens,
|
|
648
|
+
children: internalData.children,
|
|
649
|
+
infants: internalData.infants,
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// Check if any count increased
|
|
653
|
+
const hasIncrease =
|
|
654
|
+
curr.teens > prev.teens ||
|
|
655
|
+
curr.children > prev.children ||
|
|
656
|
+
curr.infants > prev.infants;
|
|
657
|
+
|
|
658
|
+
if (hasIncrease && agesSectionRef.current) {
|
|
659
|
+
// Scroll to the age section after a short delay to ensure DOM is updated
|
|
660
|
+
setTimeout(() => {
|
|
661
|
+
if (agesSectionRef.current) {
|
|
662
|
+
agesSectionRef.current.scrollIntoView({
|
|
663
|
+
behavior: 'smooth',
|
|
664
|
+
block: 'nearest',
|
|
665
|
+
inline: 'nearest',
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
}, 100);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Update previous counts
|
|
672
|
+
previousCounts.current = curr;
|
|
673
|
+
}
|
|
674
|
+
}, [
|
|
675
|
+
internalData.teens,
|
|
676
|
+
internalData.children,
|
|
677
|
+
internalData.infants,
|
|
678
|
+
multipleRooms,
|
|
679
|
+
isOpen,
|
|
680
|
+
]);
|
|
681
|
+
|
|
768
682
|
const handleDataChange = (
|
|
769
683
|
field: keyof PaxData,
|
|
770
684
|
newValue: number | ClientType | number[]
|
|
@@ -803,6 +717,17 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
803
717
|
setRooms(updatedRooms);
|
|
804
718
|
onRoomsChange?.(updatedRooms);
|
|
805
719
|
onAddRoom?.();
|
|
720
|
+
|
|
721
|
+
// Scroll to the newly added room after a short delay
|
|
722
|
+
setTimeout(() => {
|
|
723
|
+
if (lastRoomRef.current) {
|
|
724
|
+
lastRoomRef.current.scrollIntoView({
|
|
725
|
+
behavior: 'smooth',
|
|
726
|
+
block: 'nearest',
|
|
727
|
+
inline: 'nearest',
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}, 100);
|
|
806
731
|
};
|
|
807
732
|
|
|
808
733
|
const handleRemoveRoom = (roomId: string) => {
|
|
@@ -842,19 +767,22 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
842
767
|
const hasPax = getTotalPax() > 0;
|
|
843
768
|
|
|
844
769
|
return (
|
|
845
|
-
<div
|
|
770
|
+
<div
|
|
771
|
+
className={`pax-selector ${disabled ? 'pax-selector--disabled' : ''} ${className}`}
|
|
772
|
+
ref={containerRef}>
|
|
846
773
|
<button
|
|
847
774
|
ref={triggerRef}
|
|
848
775
|
type="button"
|
|
849
776
|
className="pax-selector__trigger"
|
|
850
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
777
|
+
onClick={() => !disabled && setIsOpen(!isOpen)}
|
|
851
778
|
aria-expanded={isOpen}
|
|
852
|
-
aria-haspopup="true"
|
|
779
|
+
aria-haspopup="true"
|
|
780
|
+
disabled={disabled}>
|
|
853
781
|
<Text size="sm" variant="regular" className="pax-selector__label">
|
|
854
782
|
{label}
|
|
855
783
|
</Text>
|
|
856
784
|
<div
|
|
857
|
-
className={`pax-selector__input ${isOpen ? 'pax-selector__input--active' : ''}`}>
|
|
785
|
+
className={`pax-selector__input ${isOpen ? 'pax-selector__input--active' : ''} ${disabled ? 'pax-selector__input--disabled' : ''}`}>
|
|
858
786
|
<div className="pax-selector__input-content">
|
|
859
787
|
<Icon name="user-icon" size="sm" className="pax-selector__input-icon" />
|
|
860
788
|
<span
|
|
@@ -899,6 +827,8 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
899
827
|
maxInfants={maxInfants}
|
|
900
828
|
onChange={(updatedRoom) => handleRoomChange(room.roomId, updatedRoom)}
|
|
901
829
|
onRemove={() => handleRemoveRoom(room.roomId)}
|
|
830
|
+
scrollToRef={index === rooms.length - 1 ? lastRoomRef : undefined}
|
|
831
|
+
ageRange={ageRange}
|
|
902
832
|
/>
|
|
903
833
|
))}
|
|
904
834
|
</div>
|
|
@@ -937,7 +867,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
937
867
|
{(internalData.teens > 0 ||
|
|
938
868
|
internalData.children > 0 ||
|
|
939
869
|
internalData.infants > 0) && (
|
|
940
|
-
<div className="pax-selector__age-section">
|
|
870
|
+
<div className="pax-selector__age-section" ref={agesSectionRef}>
|
|
941
871
|
<Text
|
|
942
872
|
size="base"
|
|
943
873
|
variant="bold"
|
|
@@ -988,9 +918,7 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
988
918
|
{/* Teen ages */}
|
|
989
919
|
{internalData.teens > 0 &&
|
|
990
920
|
teenAgeChunks.map((chunk, chunkIndex) => (
|
|
991
|
-
<
|
|
992
|
-
key={`teen-chunk-${chunkIndex}`}
|
|
993
|
-
className="pax-selector__age-row">
|
|
921
|
+
<Fragment key={`teen-chunk-${chunkIndex}`}>
|
|
994
922
|
{chunk.map((age, ageIndex) => {
|
|
995
923
|
const actualIndex = chunkIndex * 2 + ageIndex;
|
|
996
924
|
return (
|
|
@@ -1005,20 +933,18 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
1005
933
|
selectedAge
|
|
1006
934
|
)
|
|
1007
935
|
}
|
|
1008
|
-
ageRange={
|
|
936
|
+
ageRange={ageRange}
|
|
1009
937
|
required
|
|
1010
938
|
/>
|
|
1011
939
|
);
|
|
1012
940
|
})}
|
|
1013
|
-
</
|
|
941
|
+
</Fragment>
|
|
1014
942
|
))}
|
|
1015
943
|
|
|
1016
944
|
{/* Child ages */}
|
|
1017
945
|
{internalData.children > 0 &&
|
|
1018
946
|
childAgeChunks.map((chunk, chunkIndex) => (
|
|
1019
|
-
<
|
|
1020
|
-
key={`child-chunk-${chunkIndex}`}
|
|
1021
|
-
className="pax-selector__age-row">
|
|
947
|
+
<Fragment key={`child-chunk-${chunkIndex}`}>
|
|
1022
948
|
{chunk.map((age, ageIndex) => {
|
|
1023
949
|
const actualIndex = chunkIndex * 2 + ageIndex;
|
|
1024
950
|
return (
|
|
@@ -1033,20 +959,18 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
1033
959
|
selectedAge
|
|
1034
960
|
)
|
|
1035
961
|
}
|
|
1036
|
-
ageRange={
|
|
962
|
+
ageRange={ageRange}
|
|
1037
963
|
required
|
|
1038
964
|
/>
|
|
1039
965
|
);
|
|
1040
966
|
})}
|
|
1041
|
-
</
|
|
967
|
+
</Fragment>
|
|
1042
968
|
))}
|
|
1043
969
|
|
|
1044
970
|
{/* Infant ages */}
|
|
1045
971
|
{internalData.infants > 0 &&
|
|
1046
972
|
infantAgeChunks.map((chunk, chunkIndex) => (
|
|
1047
|
-
<
|
|
1048
|
-
key={`infant-chunk-${chunkIndex}`}
|
|
1049
|
-
className="pax-selector__age-row">
|
|
973
|
+
<Fragment key={`infant-chunk-${chunkIndex}`}>
|
|
1050
974
|
{chunk.map((age, ageIndex) => {
|
|
1051
975
|
const actualIndex = chunkIndex * 2 + ageIndex;
|
|
1052
976
|
return (
|
|
@@ -1061,12 +985,12 @@ const PaxSelector: React.FC<PaxSelectorProps> = ({
|
|
|
1061
985
|
selectedAge
|
|
1062
986
|
)
|
|
1063
987
|
}
|
|
1064
|
-
ageRange={
|
|
988
|
+
ageRange={ageRange}
|
|
1065
989
|
required
|
|
1066
990
|
/>
|
|
1067
991
|
);
|
|
1068
992
|
})}
|
|
1069
|
-
</
|
|
993
|
+
</Fragment>
|
|
1070
994
|
))}
|
|
1071
995
|
</>
|
|
1072
996
|
);
|
|
@@ -62,6 +62,8 @@ export interface RoundTripProps {
|
|
|
62
62
|
className?: string;
|
|
63
63
|
/** Whether to check if inputs are empty */
|
|
64
64
|
checkEmpty?: boolean;
|
|
65
|
+
/** Whether to scroll to the input when the calendar opens */
|
|
66
|
+
scrollOnOpen?: boolean;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
const RoundTrip: React.FC<RoundTripProps> = ({
|
|
@@ -79,6 +81,7 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
79
81
|
onChange,
|
|
80
82
|
className = "",
|
|
81
83
|
checkEmpty = false,
|
|
84
|
+
scrollOnOpen = false,
|
|
82
85
|
}) => {
|
|
83
86
|
const [internalPaxData, setInternalPaxData] = useState<PaxData | undefined>(
|
|
84
87
|
paxData
|
|
@@ -299,6 +302,7 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
299
302
|
onChange={handlePaxChange}
|
|
300
303
|
placeholder="2 pax"
|
|
301
304
|
className={isPaxEmpty ? 'pax-selector--error' : ''}
|
|
305
|
+
scrollOnOpen={scrollOnOpen}
|
|
302
306
|
/>
|
|
303
307
|
</div>
|
|
304
308
|
|
|
@@ -318,6 +322,7 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
318
322
|
defaultValue={internalArrivalDate && internalDepartureDate ? [internalArrivalDate, internalDepartureDate] : undefined}
|
|
319
323
|
inputClassName="round-trip__date-picker--input"
|
|
320
324
|
state={isDateEmpty ? 'error' : undefined}
|
|
325
|
+
scrollOnOpen={scrollOnOpen}
|
|
321
326
|
/>
|
|
322
327
|
</div>
|
|
323
328
|
<div className="round-trip__field round-trip__field--pickup-dropoff">
|
|
@@ -332,6 +337,7 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
332
337
|
type="airport-port"
|
|
333
338
|
showGroupTitles={true}
|
|
334
339
|
error={isPickupDropoffEmpty}
|
|
340
|
+
scrollOnOpen={scrollOnOpen}
|
|
335
341
|
/>
|
|
336
342
|
</div>
|
|
337
343
|
|
|
@@ -347,6 +353,7 @@ const RoundTrip: React.FC<RoundTripProps> = ({
|
|
|
347
353
|
type="accommodation"
|
|
348
354
|
showGroupTitles={false}
|
|
349
355
|
error={isAccommodationEmpty}
|
|
356
|
+
scrollOnOpen={scrollOnOpen}
|
|
350
357
|
/>
|
|
351
358
|
</div>
|
|
352
359
|
</div>
|