mautourco-components 0.2.46 → 0.2.48
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/molecules/Pagination/Pagination.css +27 -17
- package/dist/components/molecules/Pagination/Pagination.js +4 -4
- 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/Table/Table.css +64 -53
- package/dist/components/organisms/Table/Table.d.ts +1 -0
- package/dist/components/organisms/Table/Table.js +2 -1
- package/dist/components/organisms/Table/columns/booking-columns.d.ts +1 -1
- package/dist/components/organisms/Table/columns/booking-columns.js +17 -2
- package/dist/components/organisms/Table/columns/index.d.ts +1 -1
- 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/molecules/Pagination/Pagination.css +27 -18
- package/src/components/molecules/Pagination/Pagination.tsx +9 -13
- 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/Table/Table.css +64 -53
- package/src/components/organisms/Table/Table.tsx +22 -1
- package/src/components/organisms/Table/columns/booking-columns.tsx +40 -13
- 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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import '../../../styles/components/molecule/age-selector.css';
|
|
3
|
+
import Icon from '../../atoms/Icon/Icon';
|
|
4
|
+
import { Text } from '../../atoms/Typography/Typography';
|
|
5
|
+
|
|
6
|
+
export interface AgeSelectorProps {
|
|
7
|
+
/** Label for the selector */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Current selected age value */
|
|
10
|
+
value: number | undefined;
|
|
11
|
+
/** Callback when age changes */
|
|
12
|
+
onChange: (age: number) => void;
|
|
13
|
+
/** Available age range */
|
|
14
|
+
ageRange: number[];
|
|
15
|
+
/** Whether the field is required */
|
|
16
|
+
required?: boolean;
|
|
17
|
+
/** Additional CSS classes */
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Placeholder text */
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const AgeSelector: React.FC<AgeSelectorProps> = ({
|
|
24
|
+
label,
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
ageRange,
|
|
28
|
+
required = false,
|
|
29
|
+
className = '',
|
|
30
|
+
placeholder = '--',
|
|
31
|
+
}) => {
|
|
32
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
33
|
+
const [inputValue, setInputValue] = useState<string>(
|
|
34
|
+
value !== undefined ? value.toString() : ''
|
|
35
|
+
);
|
|
36
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
37
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
38
|
+
|
|
39
|
+
// Sync input value when prop value changes
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
setInputValue(value !== undefined ? value.toString() : '');
|
|
42
|
+
}, [value]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
46
|
+
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
47
|
+
setIsOpen(false);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (isOpen) {
|
|
52
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return () => {
|
|
56
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
57
|
+
};
|
|
58
|
+
}, [isOpen]);
|
|
59
|
+
|
|
60
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
61
|
+
const newValue = e.target.value;
|
|
62
|
+
|
|
63
|
+
// Only allow numeric input or empty string
|
|
64
|
+
if (newValue === '' || /^\d+$/.test(newValue)) {
|
|
65
|
+
setInputValue(newValue);
|
|
66
|
+
|
|
67
|
+
// Only update if it's a valid number within range
|
|
68
|
+
const numValue = parseInt(newValue, 10);
|
|
69
|
+
if (newValue === '') {
|
|
70
|
+
// Allow empty input - don't update onChange
|
|
71
|
+
return;
|
|
72
|
+
} else if (!isNaN(numValue) && ageRange.includes(numValue)) {
|
|
73
|
+
onChange(numValue);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleInputBlur = () => {
|
|
79
|
+
// Validate and set to valid value or clear if invalid
|
|
80
|
+
const numValue = parseInt(inputValue, 10);
|
|
81
|
+
if (inputValue === '') {
|
|
82
|
+
// Keep empty if user cleared it
|
|
83
|
+
return;
|
|
84
|
+
} else if (isNaN(numValue) || !ageRange.includes(numValue)) {
|
|
85
|
+
// Reset to current value if invalid
|
|
86
|
+
setInputValue(value !== undefined ? value.toString() : '');
|
|
87
|
+
} else {
|
|
88
|
+
// Ensure the input value matches the validated value
|
|
89
|
+
setInputValue(numValue.toString());
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
94
|
+
if (e.key === 'Enter') {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
inputRef.current?.blur();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleSelect = (age: string) => {
|
|
101
|
+
const numAge = parseInt(age, 10);
|
|
102
|
+
onChange(numAge);
|
|
103
|
+
setIsOpen(false);
|
|
104
|
+
setInputValue(age);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const handleDropdownToggle = () => {
|
|
108
|
+
setIsOpen(!isOpen);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const ageOptions = ageRange.map((age) => age.toString());
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div className={`age-selector ${className}`} ref={containerRef}>
|
|
115
|
+
<Text size="sm" variant="regular" className="age-selector__label">
|
|
116
|
+
{label}
|
|
117
|
+
{required && <span className="age-selector__required"> *</span>}
|
|
118
|
+
</Text>
|
|
119
|
+
<div className="age-selector__container">
|
|
120
|
+
<div
|
|
121
|
+
className={`age-selector__input ${isOpen ? 'age-selector__input--open' : ''} ${value !== undefined ? 'age-selector__input--selected' : 'age-selector__input--default'}`}>
|
|
122
|
+
<input
|
|
123
|
+
ref={inputRef}
|
|
124
|
+
type="text"
|
|
125
|
+
inputMode="numeric"
|
|
126
|
+
className="age-selector__input-field"
|
|
127
|
+
value={inputValue}
|
|
128
|
+
onChange={handleInputChange}
|
|
129
|
+
onBlur={handleInputBlur}
|
|
130
|
+
onKeyDown={handleInputKeyDown}
|
|
131
|
+
onFocus={() => setIsOpen(false)}
|
|
132
|
+
placeholder={placeholder}
|
|
133
|
+
aria-label={`${label} age`}
|
|
134
|
+
/>
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
className="age-selector__dropdown-btn"
|
|
138
|
+
onClick={(e) => {
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
handleDropdownToggle();
|
|
142
|
+
}}
|
|
143
|
+
aria-expanded={isOpen}
|
|
144
|
+
aria-haspopup="listbox"
|
|
145
|
+
aria-label="Open age dropdown">
|
|
146
|
+
<Icon
|
|
147
|
+
name="chevron-down"
|
|
148
|
+
size="sm"
|
|
149
|
+
className={`age-selector__icon ${isOpen ? 'age-selector__icon--open' : ''}`}
|
|
150
|
+
/>
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
{isOpen && (
|
|
154
|
+
<div className="age-selector__dropdown" role="listbox">
|
|
155
|
+
{ageOptions.map((age) => (
|
|
156
|
+
<div
|
|
157
|
+
key={age}
|
|
158
|
+
className={`age-selector__option ${value?.toString() === age ? 'age-selector__option--selected' : ''}`}
|
|
159
|
+
onClick={() => handleSelect(age)}
|
|
160
|
+
role="option"
|
|
161
|
+
aria-selected={value?.toString() === age}>
|
|
162
|
+
{age}
|
|
163
|
+
</div>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export default AgeSelector;
|
|
@@ -189,7 +189,7 @@ const LocationDropdown: React.FC<LocationDropdownProps> = ({
|
|
|
189
189
|
return (
|
|
190
190
|
<div
|
|
191
191
|
ref={dropdownRef}
|
|
192
|
-
className={`location-dropdown location-dropdown--${type} ${className}`}>
|
|
192
|
+
className={`location-dropdown location-dropdown--${type} ${disabled ? 'location-dropdown--disabled' : ''} ${className}`}>
|
|
193
193
|
{label && (
|
|
194
194
|
<div className="location-dropdown__label">
|
|
195
195
|
<Text size="sm" variant="medium">
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
.pagination {
|
|
1
|
+
.mtc-pagination {
|
|
2
2
|
display: flex;
|
|
3
3
|
align-items: center;
|
|
4
4
|
gap: var(--spacing-gap-gap-3, 12px);
|
|
5
|
-
padding: var(--spacing-gap-gap-3, 12px) var(--spacing-gap-gap-3, 12px)
|
|
5
|
+
padding: var(--spacing-gap-gap-3, 12px) var(--spacing-gap-gap-3, 12px)
|
|
6
|
+
var(--spacing-gap-gap-1-5, 6px);
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
.pagination__button {
|
|
9
|
+
.mtc-pagination__button {
|
|
9
10
|
display: flex;
|
|
10
11
|
align-items: center;
|
|
11
12
|
justify-content: center;
|
|
@@ -14,35 +15,38 @@
|
|
|
14
15
|
border-radius: var(--border-radius-rounded-lg, 8px);
|
|
15
16
|
padding: var(--spacing-padding-py-2, 8px) var(--spacing-padding-px-2, 8px);
|
|
16
17
|
cursor: pointer;
|
|
17
|
-
transition:
|
|
18
|
+
transition:
|
|
19
|
+
background-color 0.2s ease-in-out,
|
|
20
|
+
border-color 0.2s ease-in-out,
|
|
21
|
+
opacity 0.2s ease-in-out;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
.pagination__button:hover:not(:disabled) {
|
|
24
|
+
.mtc-pagination__button:hover:not(:disabled) {
|
|
21
25
|
background-color: var(--color-elevation-state-hover-subtle, #ededed);
|
|
22
26
|
border-color: var(--color-border-medium, #a3a3a3);
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
.pagination__button:active:not(:disabled) {
|
|
29
|
+
.mtc-pagination__button:active:not(:disabled) {
|
|
26
30
|
background-color: var(--color-elevation-state-pressed-subtle, #d9d9d9);
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
.pagination__button:disabled {
|
|
33
|
+
.mtc-pagination__button:disabled {
|
|
30
34
|
opacity: 0.5;
|
|
31
35
|
cursor: not-allowed;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
.pagination__button:focus-visible {
|
|
38
|
+
.mtc-pagination__button:focus-visible {
|
|
35
39
|
outline: 2px solid var(--color-atoll-green-800, #0f7173);
|
|
36
40
|
outline-offset: 2px;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
.pagination__numbers {
|
|
43
|
+
.mtc-pagination__numbers {
|
|
40
44
|
display: flex;
|
|
41
45
|
align-items: center;
|
|
42
46
|
gap: var(--spacing-gap-gap-1, 4px);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
.pagination__number {
|
|
49
|
+
.mtc-pagination__number {
|
|
46
50
|
display: flex;
|
|
47
51
|
align-items: center;
|
|
48
52
|
justify-content: center;
|
|
@@ -51,40 +55,45 @@
|
|
|
51
55
|
background-color: var(--color-elevation-level-1, #ffffff);
|
|
52
56
|
border: none;
|
|
53
57
|
border-radius: var(--border-radius-rounded-lg, 8px);
|
|
54
|
-
font-family:
|
|
58
|
+
font-family:
|
|
59
|
+
var(--font-font-family-body, 'Satoshi'), 'Satoshi', 'Inter', 'Segoe UI', 'system-ui',
|
|
60
|
+
sans-serif;
|
|
55
61
|
font-size: var(--font-size-text-sm, 14px);
|
|
56
62
|
font-weight: var(--font-weight-font-medium, 500);
|
|
57
63
|
line-height: calc(var(--font-leading-leading-sm, 20) * 1px);
|
|
58
64
|
color: var(--color-text-default, #262626);
|
|
59
65
|
cursor: pointer;
|
|
60
|
-
transition:
|
|
66
|
+
transition:
|
|
67
|
+
background-color 0.2s ease-in-out,
|
|
68
|
+
color 0.2s ease-in-out;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
.pagination__number:hover:not(.pagination__number--selected) {
|
|
71
|
+
.mtc-pagination__number:hover:not(.mtc-pagination__number--selected) {
|
|
64
72
|
background-color: var(--color-elevation-level-2, #f5f5f5);
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
.pagination__number--selected {
|
|
75
|
+
.mtc-pagination__number--selected {
|
|
68
76
|
background-color: var(--color-atoll-green-800, #0f7173);
|
|
69
77
|
color: var(--color-text-inverted, #ffffff);
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
.pagination__number:focus-visible {
|
|
80
|
+
.mtc-pagination__number:focus-visible {
|
|
73
81
|
outline: 2px solid var(--color-atoll-green-800, #0f7173);
|
|
74
82
|
outline-offset: 2px;
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
.pagination__ellipsis {
|
|
85
|
+
.mtc-pagination__ellipsis {
|
|
78
86
|
display: flex;
|
|
79
87
|
align-items: center;
|
|
80
88
|
justify-content: center;
|
|
81
89
|
width: var(--dimension-width-w-10, 40px);
|
|
82
90
|
height: var(--dimension-height-h-10, 40px);
|
|
83
|
-
font-family:
|
|
91
|
+
font-family:
|
|
92
|
+
var(--font-font-family-body, 'Satoshi'), 'Satoshi', 'Inter', 'Segoe UI', 'system-ui',
|
|
93
|
+
sans-serif;
|
|
84
94
|
font-size: var(--font-size-text-sm, 14px);
|
|
85
95
|
font-weight: var(--font-weight-font-medium, 500);
|
|
86
96
|
line-height: calc(var(--font-leading-leading-sm, 20) * 1px);
|
|
87
97
|
color: var(--color-text-default, #262626);
|
|
88
98
|
user-select: none;
|
|
89
99
|
}
|
|
90
|
-
|
|
@@ -94,22 +94,21 @@ const Pagination: React.FC<PaginationProps> = ({
|
|
|
94
94
|
const isNextDisabled = currentPage >= totalPages;
|
|
95
95
|
|
|
96
96
|
return (
|
|
97
|
-
<div className={`pagination ${className}`}>
|
|
97
|
+
<div className={`mtc-pagination ${className}`}>
|
|
98
98
|
<button
|
|
99
99
|
type="button"
|
|
100
|
-
className="pagination__button pagination__button--prev"
|
|
100
|
+
className="mtc-pagination__button mtc-pagination__button--prev"
|
|
101
101
|
onClick={handlePrevious}
|
|
102
102
|
disabled={isPreviousDisabled}
|
|
103
|
-
aria-label="Previous page"
|
|
104
|
-
>
|
|
103
|
+
aria-label="Previous page">
|
|
105
104
|
<Icon name="chevron-left" size="sm" />
|
|
106
105
|
</button>
|
|
107
106
|
|
|
108
|
-
<div className="pagination__numbers">
|
|
107
|
+
<div className="mtc-pagination__numbers">
|
|
109
108
|
{pageNumbers.map((page, index) => {
|
|
110
109
|
if (page === 'ellipsis') {
|
|
111
110
|
return (
|
|
112
|
-
<span key={`ellipsis-${index}`} className="pagination__ellipsis">
|
|
111
|
+
<span key={`ellipsis-${index}`} className="mtc-pagination__ellipsis">
|
|
113
112
|
...
|
|
114
113
|
</span>
|
|
115
114
|
);
|
|
@@ -121,11 +120,10 @@ const Pagination: React.FC<PaginationProps> = ({
|
|
|
121
120
|
<button
|
|
122
121
|
key={page}
|
|
123
122
|
type="button"
|
|
124
|
-
className={`pagination__number ${isSelected ? 'pagination__number--selected' : ''}`}
|
|
123
|
+
className={`mtc-pagination__number ${isSelected ? 'mtc-pagination__number--selected' : ''}`}
|
|
125
124
|
onClick={() => handlePageClick(page)}
|
|
126
125
|
aria-label={`Page ${page}`}
|
|
127
|
-
aria-current={isSelected ? 'page' : undefined}
|
|
128
|
-
>
|
|
126
|
+
aria-current={isSelected ? 'page' : undefined}>
|
|
129
127
|
{page}
|
|
130
128
|
</button>
|
|
131
129
|
);
|
|
@@ -134,11 +132,10 @@ const Pagination: React.FC<PaginationProps> = ({
|
|
|
134
132
|
|
|
135
133
|
<button
|
|
136
134
|
type="button"
|
|
137
|
-
className="pagination__button pagination__button--next"
|
|
135
|
+
className="mtc-pagination__button mtc-pagination__button--next"
|
|
138
136
|
onClick={handleNext}
|
|
139
137
|
disabled={isNextDisabled}
|
|
140
|
-
aria-label="Next page"
|
|
141
|
-
>
|
|
138
|
+
aria-label="Next page">
|
|
142
139
|
<Icon name="chevron-right" size="sm" />
|
|
143
140
|
</button>
|
|
144
141
|
</div>
|
|
@@ -146,4 +143,3 @@ const Pagination: React.FC<PaginationProps> = ({
|
|
|
146
143
|
};
|
|
147
144
|
|
|
148
145
|
export default Pagination;
|
|
149
|
-
|