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.
@@ -156,11 +156,42 @@
156
156
  opacity: 0.6;
157
157
  }
158
158
 
159
- .chip--clickable:focus {
159
+ /* Focus styles - specific to each color */
160
+ .chip--clickable.chip--brand:focus {
160
161
  outline: 2px solid var(--chip-color-brand-outline-foreground, #ed4c09);
161
162
  outline-offset: 2px;
162
163
  }
163
164
 
165
+ .chip--clickable.chip--accent:focus {
166
+ outline: 2px solid var(--chip-color-accent-outline-foreground, #0f7173);
167
+ outline-offset: 2px;
168
+ }
169
+
170
+ .chip--clickable.chip--blue:focus {
171
+ outline: 2px solid var(--chip-color-blue-outline-foreground, #2e4780);
172
+ outline-offset: 2px;
173
+ }
174
+
175
+ .chip--clickable.chip--green:focus {
176
+ outline: 2px solid var(--chip-color-green-outline-foreground, #4a6045);
177
+ outline-offset: 2px;
178
+ }
179
+
180
+ .chip--clickable.chip--yellow:focus {
181
+ outline: 2px solid var(--chip-color-yellow-outline-foreground, #eab308);
182
+ outline-offset: 2px;
183
+ }
184
+
185
+ .chip--clickable.chip--red:focus {
186
+ outline: 2px solid var(--chip-color-red-outline-foreground, #991b1b);
187
+ outline-offset: 2px;
188
+ }
189
+
190
+ .chip--clickable.chip--neutral:focus {
191
+ outline: 2px solid var(--chip-color-neutral-outline-foreground, #9ca3af);
192
+ outline-offset: 2px;
193
+ }
194
+
164
195
  .chip--outline.chip--black-text {
165
196
  color: var(--chip-color-neutral-outline-text, #262626);
166
197
  }
@@ -0,0 +1,81 @@
1
+ .vehicle-supplement {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--spacing-gap-gap-6, 24px);
5
+ padding: var(--spacing-padding-px-0, 0px);
6
+ }
7
+
8
+ .vehicle-supplement__content {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: var(--spacing-gap-gap-4, 16px);
12
+ }
13
+
14
+ .vehicle-supplement__transfer-section {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: var(--spacing-gap-gap-4, 16px);
18
+ }
19
+
20
+ .vehicle-supplement__transfer-header {
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: space-between;
24
+ height: 44px;
25
+ padding: var(--spacing-gap-gap-0, 0px);
26
+ }
27
+
28
+ .vehicle-supplement__transfer-title {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: var(--spacing-gap-gap-2, 8px);
32
+ }
33
+
34
+ .vehicle-supplement__transfer-label {
35
+ font-family: var(--font-font-family-body, "Satoshi"), "Inter", "Segoe UI", "system-ui", sans-serif;
36
+ font-weight: var(--font-weight-font-bold, 700);
37
+ font-size: var(--font-size-text-base, 16px);
38
+ line-height: calc(var(--font-leading-leading-md, 24) * 1px);
39
+ color: var(--checkbox-color-label-default, #303642);
40
+ }
41
+
42
+ .vehicle-supplement__supplements-list {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: var(--spacing-gap-gap-3, 12px);
46
+ }
47
+
48
+ .vehicle-supplement__supplement-row {
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: space-between;
52
+ height: 32px;
53
+ padding: var(--spacing-padding-px-0, 0px);
54
+ }
55
+
56
+ .vehicle-supplement__supplement-name {
57
+ font-family: var(--font-font-family-body, "Satoshi"), "Inter", "Segoe UI", "system-ui", sans-serif;
58
+ font-weight: var(--font-weight-font-medium, 500);
59
+ font-size: var(--font-size-text-base, 16px);
60
+ line-height: calc(var(--font-leading-leading-md, 24) * 1px);
61
+ color: var(--checkbox-color-label-default, #303642);
62
+ }
63
+
64
+ .vehicle-supplement__divider {
65
+ width: 100%;
66
+ height: 0;
67
+ border-top: 1px solid var(--color-border-default, #d9d9d9);
68
+ }
69
+
70
+ .vehicle-supplement__footer {
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: flex-end;
74
+ gap: var(--spacing-gap-gap-4, 16px);
75
+ height: 36px;
76
+ }
77
+
78
+ .vehicle-supplement__cancel-button,
79
+ .vehicle-supplement__done-button {
80
+ min-width: 114px;
81
+ }
@@ -0,0 +1,206 @@
1
+ import React, { useState } from 'react';
2
+ import { TransferDocket } from '../../../types/docket/services.types';
3
+ import Button from '../../atoms/Button/Button';
4
+ import Chip from '../../atoms/Chip/Chip';
5
+ import Icon from '../../atoms/Icon/Icon';
6
+ import Stepper from '../Stepper/Stepper';
7
+ import './VehicleSupplement.css';
8
+
9
+ export interface Supplement {
10
+ supplement_name: string;
11
+ max?: number;
12
+ min?: number;
13
+ }
14
+
15
+ export interface SupplementValue {
16
+ transferId: number;
17
+ supplementName: string;
18
+ value: number;
19
+ }
20
+
21
+ export interface VehicleSupplementProps {
22
+ /** Array of available supplements */
23
+ supplements: Supplement[];
24
+ /** Array of transfers (Arrival, Inter-Hotel, Departure) */
25
+ transfer: TransferDocket[];
26
+ /** Handler called when supplement values change */
27
+ onChange: (values: SupplementValue[]) => void;
28
+ /** Handler called when Done button is clicked */
29
+ onDone: (values: SupplementValue[]) => void;
30
+ /** Handler called when Clear/Cancel button is clicked */
31
+ onClear: () => void;
32
+ /** Initial values for the supplements (optional) */
33
+ initialValues?: SupplementValue[];
34
+ /** Additional CSS classes */
35
+ className?: string;
36
+ }
37
+
38
+ const VehicleSupplement: React.FC<VehicleSupplementProps> = ({
39
+ supplements,
40
+ transfer,
41
+ onChange,
42
+ onDone,
43
+ onClear,
44
+ initialValues = [],
45
+ className = '',
46
+ }) => {
47
+ // Initialize state for all supplements across all transfers
48
+ const [supplementValues, setSupplementValues] = useState<SupplementValue[]>(() => {
49
+ // If initial values are provided, use them
50
+ if (initialValues.length > 0) {
51
+ return initialValues;
52
+ }
53
+
54
+ // Otherwise, create default values with 0
55
+ const defaultValues: SupplementValue[] = [];
56
+ transfer.forEach((t) => {
57
+ supplements.forEach((s) => {
58
+ defaultValues.push({
59
+ transferId: t.IdTransfer,
60
+ supplementName: s.supplement_name,
61
+ value: 0,
62
+ });
63
+ });
64
+ });
65
+ return defaultValues;
66
+ });
67
+
68
+ const getValue = (transferId: number, supplementName: string): number => {
69
+ const item = supplementValues.find(
70
+ (v) => v.transferId === transferId && v.supplementName === supplementName
71
+ );
72
+ return item?.value || 0;
73
+ };
74
+
75
+ const handleSupplementChange = (
76
+ transferId: number,
77
+ supplementName: string,
78
+ newValue: number
79
+ ) => {
80
+ const updatedValues = supplementValues.map((v) =>
81
+ v.transferId === transferId && v.supplementName === supplementName
82
+ ? { ...v, value: newValue }
83
+ : v
84
+ );
85
+ setSupplementValues(updatedValues);
86
+ onChange(updatedValues);
87
+ };
88
+
89
+ const getTransferTypeIcon = (transferType: string) => {
90
+ const type = transferType.toLowerCase();
91
+ if (type.includes('arrival') || type.includes('airport') || type.includes('inbound')) {
92
+ return 'plane-landing-outline';
93
+ }
94
+ if (type.includes('departure') || type.includes('outbound')) {
95
+ return 'plane-takeoff-outline';
96
+ }
97
+ return 'building-2-outline';
98
+ };
99
+
100
+ const getTransferTypeLabel = (transferType: string): string => {
101
+ const type = transferType.toLowerCase();
102
+ if (type.includes('arrival') || type.includes('airport') || type.includes('inbound')) {
103
+ return 'Arrival';
104
+ }
105
+ if (type.includes('departure') || type.includes('outbound')) {
106
+ return 'Departure';
107
+ }
108
+ return 'Inter-Hotel';
109
+ };
110
+
111
+ const formatDate = (dateStr: string): string => {
112
+ try {
113
+ const date = new Date(dateStr);
114
+ const day = String(date.getDate()).padStart(2, '0');
115
+ const month = String(date.getMonth() + 1).padStart(2, '0');
116
+ const year = date.getFullYear();
117
+ return `${day}/${month}/${year}`;
118
+ } catch {
119
+ return dateStr;
120
+ }
121
+ };
122
+
123
+ const handleDone = () => {
124
+ onDone(supplementValues);
125
+ };
126
+
127
+ const baseClass = 'vehicle-supplement';
128
+ const classes = [baseClass, className].filter(Boolean).join(' ');
129
+
130
+ return (
131
+ <div className={classes}>
132
+ <div className={`${baseClass}__content`}>
133
+ {transfer.map((t, index) => {
134
+ const isLast = index === transfer.length - 1;
135
+ const typeLabel = getTransferTypeLabel(t.TransferType);
136
+ const iconName = getTransferTypeIcon(t.TransferType);
137
+
138
+ return (
139
+ <div key={t.IdTransfer} className={`${baseClass}__transfer-section`}>
140
+ <div className={`${baseClass}__transfer-header`}>
141
+ <div className={`${baseClass}__transfer-title`}>
142
+ <Icon name={iconName as any} size="sm" />
143
+ <span className={`${baseClass}__transfer-label`}>{typeLabel}</span>
144
+ </div>
145
+ <Chip
146
+ leadingIcon="calendar"
147
+ label={formatDate(t.TransferDate)}
148
+ size="sm"
149
+ color="neutral"
150
+ isBlackText
151
+ />
152
+ </div>
153
+
154
+ <div className={`${baseClass}__supplements-list`}>
155
+ {supplements.map((supplement) => {
156
+ const currentValue = getValue(t.IdTransfer, supplement.supplement_name);
157
+ return (
158
+ <div
159
+ key={supplement.supplement_name}
160
+ className={`${baseClass}__supplement-row`}
161
+ >
162
+ <span className={`${baseClass}__supplement-name`}>
163
+ {supplement.supplement_name}
164
+ </span>
165
+ <Stepper
166
+ value={currentValue}
167
+ min={supplement.min || 0}
168
+ max={supplement.max || 99}
169
+ onChange={(value) =>
170
+ handleSupplementChange(t.IdTransfer, supplement.supplement_name, value)
171
+ }
172
+ />
173
+ </div>
174
+ );
175
+ })}
176
+ </div>
177
+
178
+ {!isLast && <div className={`${baseClass}__divider`} />}
179
+ </div>
180
+ );
181
+ })}
182
+ </div>
183
+
184
+ <div className={`${baseClass}__footer`}>
185
+ <Button
186
+ variant="outline-secondary"
187
+ size="sm"
188
+ onClick={onClear}
189
+ className={`${baseClass}__cancel-button`}
190
+ >
191
+ Cancel
192
+ </Button>
193
+ <Button
194
+ variant="secondary"
195
+ size="sm"
196
+ onClick={handleDone}
197
+ className={`${baseClass}__done-button`}
198
+ >
199
+ Done
200
+ </Button>
201
+ </div>
202
+ </div>
203
+ );
204
+ };
205
+
206
+ export default VehicleSupplement;
@@ -0,0 +1,2 @@
1
+ export { default } from './VehicleSupplement';
2
+ export type { VehicleSupplementProps, Supplement, SupplementValue } from './VehicleSupplement';
@@ -133,12 +133,15 @@
133
133
 
134
134
  .car-booking-card__supplement-message {
135
135
  margin: 0;
136
- color: var(--color-messaging-danger-foreground, #a01414);
137
136
  white-space: normal;
138
137
  text-align: right;
139
138
  flex: 1 1 auto;
140
139
  }
141
140
 
141
+ .car-booking-card__supplement-header .car-booking-card__supplement-message--error {
142
+ color: var(--color-messaging-danger-foreground, #a01414);
143
+ }
144
+
142
145
  .car-booking-card__section--supplement {
143
146
  /* Control spacing precisely: divider -> header != header -> select */
144
147
  gap: 0;
@@ -245,4 +248,121 @@
245
248
  white-space: nowrap;
246
249
  }
247
250
 
251
+ /* Supplement dropdown styles */
252
+ .car-booking-card__supplement-dropdown {
253
+ position: relative;
254
+ width: 100%;
255
+ }
256
+
257
+ .car-booking-card__supplement-trigger {
258
+ width: 100%;
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: space-between;
262
+ gap: var(--spacing-gap-gap-2, 8px);
263
+ padding: var(--spacing-padding-py-3, 12px) var(--spacing-padding-px-4, 16px);
264
+ background: var(--color-elevation-level-1, #ffffff);
265
+ border: var(--input-border-width-default, 1px) solid var(--color-border-default, #d4d4d4);
266
+ border-radius: var(--border-radius-rounded-xl, 12px);
267
+ font-family: var(--font-font-family-body, "Satoshi"), "Inter", "Segoe UI", "system-ui", sans-serif;
268
+ font-size: var(--font-size-text-base, 16px);
269
+ line-height: calc(var(--font-leading-leading-md, 24) * 1px);
270
+ color: var(--color-text-default, #262626);
271
+ cursor: pointer;
272
+ transition: border-color 0.2s ease, background-color 0.2s ease;
273
+ min-height: 48px;
274
+ }
275
+
276
+ .car-booking-card__supplement-trigger:hover:not(:disabled) {
277
+ border-color: var(--color-border-active-default, #0f7173);
278
+ }
279
+
280
+ .car-booking-card__supplement-trigger:focus {
281
+ outline: var(--border-width-focus, 2px) solid var(--color-border-active-default, #0f7173);
282
+ outline-offset: var(--spacing-base-0-5, 2px);
283
+ }
284
+
285
+ .car-booking-card__supplement-trigger--disabled {
286
+ background: var(--color-elevation-level-2, #f5f5f5);
287
+ cursor: not-allowed;
288
+ opacity: 0.6;
289
+ }
290
+
291
+ .car-booking-card__supplement-placeholder {
292
+ color: var(--color-text-subtle, #737373);
293
+ font-weight: var(--font-weight-font-regular, 400);
294
+ }
295
+
296
+ .car-booking-card__supplement-trigger--has-value .car-booking-card__supplement-placeholder {
297
+ color: var(--color-text-default, #262626);
298
+ }
299
+
300
+ .car-booking-card__supplement-chips {
301
+ display: flex;
302
+ flex-wrap: wrap;
303
+ gap: var(--spacing-gap-gap-2, 8px);
304
+ flex: 1;
305
+ align-items: center;
306
+ }
307
+
308
+ /* Ensure chips are properly interactive within the button */
309
+ .car-booking-card__supplement-chips .chip {
310
+ cursor: pointer;
311
+ transition: opacity 0.15s ease, transform 0.15s ease;
312
+ user-select: none;
313
+ }
314
+
315
+ /* Hover state for chips - subtle scale and opacity change */
316
+ .car-booking-card__supplement-chips .chip:hover {
317
+ opacity: 0.85;
318
+ transform: translateY(-1px);
319
+ }
320
+
321
+ /* Active/pressed state for chips */
322
+ .car-booking-card__supplement-chips .chip:active {
323
+ opacity: 0.7;
324
+ transform: translateY(0);
325
+ }
326
+
327
+ /* Make close icon more prominent on chip hover */
328
+ .car-booking-card__supplement-chips .chip:hover .chip__icon--trailing {
329
+ opacity: 1;
330
+ transform: scale(1.1);
331
+ }
332
+
333
+ /* Transition for chip icons */
334
+ .car-booking-card__supplement-chips .chip .chip__icon--trailing {
335
+ transition: transform 0.15s ease, opacity 0.15s ease;
336
+ opacity: 0.8;
337
+ }
338
+
339
+ .car-booking-card__supplement-icon {
340
+ transition: transform 0.2s ease;
341
+ flex-shrink: 0;
342
+ }
343
+
344
+ .car-booking-card__supplement-icon--open {
345
+ transform: rotate(180deg);
346
+ }
347
+
348
+ .car-booking-card__supplement-panel {
349
+ position: absolute;
350
+ top: calc(100% + var(--spacing-gap-gap-2, 8px));
351
+ left: 0;
352
+ right: 0;
353
+ z-index: 10;
354
+ background: var(--color-elevation-level-1, #ffffff);
355
+ border-radius: var(--border-radius-rounded-xl, 12px);
356
+ box-shadow:
357
+ var(--spacing-base-0, 0px) var(--spacing-base-1, 4px)
358
+ var(--backdrop-blur-backdrop-blur, 8px) var(--spacing-base-0, 0px)
359
+ rgba(48, 54, 66, 0.1),
360
+ var(--spacing-base-0, 0px) var(--spacing-base-px, 1px)
361
+ var(--backdrop-blur-backdrop-blur, 8px) var(--spacing-base-0, 0px)
362
+ rgba(48, 54, 66, 0.11);
363
+ padding: var(--spacing-padding-py-6, 24px);
364
+ max-height: 600px;
365
+ overflow-y: auto;
366
+ }
367
+
248
368