@citizenplane/pimp 18.10.3 → 18.11.1
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/CpSeatMap.vue.d.ts +29 -0
- package/dist/components/CpSeatMap.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapEmergencyExitDialog.vue.d.ts +21 -0
- package/dist/components/CpSeatMapEmergencyExitDialog.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapGrid.vue.d.ts +17 -0
- package/dist/components/CpSeatMapGrid.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapPlane.vue.d.ts +24 -0
- package/dist/components/CpSeatMapPlane.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapRow.vue.d.ts +22 -0
- package/dist/components/CpSeatMapRow.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapRowFacility.vue.d.ts +16 -0
- package/dist/components/CpSeatMapRowFacility.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapSeat.vue.d.ts +21 -0
- package/dist/components/CpSeatMapSeat.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapSeatWrapper.vue.d.ts +23 -0
- package/dist/components/CpSeatMapSeatWrapper.vue.d.ts.map +1 -0
- package/dist/components/CpSeatMapZone.vue.d.ts +16 -0
- package/dist/components/CpSeatMapZone.vue.d.ts.map +1 -0
- package/dist/components/CpTabs.vue.d.ts.map +1 -1
- package/dist/components/icons/IconPlaneNose.vue.d.ts +3 -0
- package/dist/components/icons/IconPlaneNose.vue.d.ts.map +1 -0
- package/dist/components/icons/IconPlaneWing.vue.d.ts +3 -0
- package/dist/components/icons/IconPlaneWing.vue.d.ts.map +1 -0
- package/dist/components/icons/IconSparkle.vue.d.ts +3 -0
- package/dist/components/icons/IconSparkle.vue.d.ts.map +1 -0
- package/dist/components/index.d.ts +14 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/constants/seatMap/CpSeatMapConfig.d.ts +22 -0
- package/dist/constants/seatMap/CpSeatMapConfig.d.ts.map +1 -0
- package/dist/constants/seatMap/CpSeatMapSeatSelection.d.ts +12 -0
- package/dist/constants/seatMap/CpSeatMapSeatSelection.d.ts.map +1 -0
- package/dist/constants/seatMap/CpSeatMapTransitionDirections.d.ts +5 -0
- package/dist/constants/seatMap/CpSeatMapTransitionDirections.d.ts.map +1 -0
- package/dist/constants/seatMap/IataTravelerTypes.d.ts +7 -0
- package/dist/constants/seatMap/IataTravelerTypes.d.ts.map +1 -0
- package/dist/constants/seatMap/Seat.d.ts +18 -0
- package/dist/constants/seatMap/Seat.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatAncillary.d.ts +9 -0
- package/dist/constants/seatMap/SeatAncillary.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatCharacteristic.d.ts +7 -0
- package/dist/constants/seatMap/SeatCharacteristic.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatCharacteristicCodes.d.ts +10 -0
- package/dist/constants/seatMap/SeatCharacteristicCodes.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatMap.d.ts +10 -0
- package/dist/constants/seatMap/SeatMap.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatMapZone.d.ts +6 -0
- package/dist/constants/seatMap/SeatMapZone.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatRow.d.ts +15 -0
- package/dist/constants/seatMap/SeatRow.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatRowFacilities.d.ts +10 -0
- package/dist/constants/seatMap/SeatRowFacilities.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatRowFacilityFlags.d.ts +8 -0
- package/dist/constants/seatMap/SeatRowFacilityFlags.d.ts.map +1 -0
- package/dist/constants/seatMap/SeatStatuses.d.ts +8 -0
- package/dist/constants/seatMap/SeatStatuses.d.ts.map +1 -0
- package/dist/constants/seatMap/index.d.ts +16 -0
- package/dist/constants/seatMap/index.d.ts.map +1 -0
- package/dist/helpers/seatMap.d.ts +28 -0
- package/dist/helpers/seatMap.d.ts.map +1 -0
- package/dist/pimp.es.js +4265 -3758
- package/dist/pimp.umd.js +40 -40
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CpSeatMap.vue +226 -0
- package/src/components/CpSeatMapEmergencyExitDialog.vue +87 -0
- package/src/components/CpSeatMapGrid.vue +41 -0
- package/src/components/CpSeatMapPlane.vue +221 -0
- package/src/components/CpSeatMapRow.vue +183 -0
- package/src/components/CpSeatMapRowFacility.vue +88 -0
- package/src/components/CpSeatMapSeat.vue +138 -0
- package/src/components/CpSeatMapSeatWrapper.vue +189 -0
- package/src/components/CpSeatMapZone.vue +60 -0
- package/src/components/CpTabs.vue +7 -12
- package/src/components/icons/IconPlaneNose.vue +39 -0
- package/src/components/icons/IconPlaneWing.vue +11 -0
- package/src/components/icons/IconSparkle.vue +8 -0
- package/src/components/index.ts +51 -0
- package/src/constants/seatMap/CpSeatMapConfig.ts +47 -0
- package/src/constants/seatMap/CpSeatMapSeatSelection.ts +12 -0
- package/src/constants/seatMap/CpSeatMapTransitionDirections.ts +4 -0
- package/src/constants/seatMap/IataTravelerTypes.ts +6 -0
- package/src/constants/seatMap/Seat.ts +18 -0
- package/src/constants/seatMap/SeatAncillary.ts +8 -0
- package/src/constants/seatMap/SeatCharacteristic.ts +7 -0
- package/src/constants/seatMap/SeatCharacteristicCodes.ts +9 -0
- package/src/constants/seatMap/SeatMap.ts +10 -0
- package/src/constants/seatMap/SeatMapZone.ts +5 -0
- package/src/constants/seatMap/SeatRow.ts +15 -0
- package/src/constants/seatMap/SeatRowFacilities.ts +10 -0
- package/src/constants/seatMap/SeatRowFacilityFlags.ts +7 -0
- package/src/constants/seatMap/SeatStatuses.ts +7 -0
- package/src/constants/seatMap/index.ts +16 -0
- package/src/helpers/seatMap.ts +76 -0
- package/src/stories/CpSeatMap.stories.ts +357 -0
package/package.json
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpSeatMap" :style="seatMapDynamicStyle" :class="seatMapClasses">
|
|
3
|
+
<cp-seat-map-plane
|
|
4
|
+
:key="seatMap.aircraft"
|
|
5
|
+
class="cpSeatMap__plane"
|
|
6
|
+
:is-large="isLargePlane"
|
|
7
|
+
:map-key="seatMap.ref_id_segment"
|
|
8
|
+
:transition-direction="transitionDirection"
|
|
9
|
+
>
|
|
10
|
+
<cp-seat-map-zone v-for="zone in zones" :key="zone.id" class="cpSeatMap__zone" :title="zone.title">
|
|
11
|
+
<cp-seat-map-grid class="cpSeatMap__grid" :layout="zone.layout" :style="getGridDynamicStyle(zone.layout)">
|
|
12
|
+
<cp-seat-map-row
|
|
13
|
+
v-for="row in getZoneRows(seatMap, zone.id)"
|
|
14
|
+
:key="row.number"
|
|
15
|
+
class="cpSeatMap__row"
|
|
16
|
+
:config="config"
|
|
17
|
+
:row="row"
|
|
18
|
+
:seat-block-size="getSeatBlockSize(zone.layout)"
|
|
19
|
+
>
|
|
20
|
+
<cp-seat-map-seat-wrapper
|
|
21
|
+
v-for="seat in row.seats"
|
|
22
|
+
:key="seat.id"
|
|
23
|
+
:config="config"
|
|
24
|
+
:is-highlighted="highlightedSeatId === seat.id"
|
|
25
|
+
:is-selection-enabled="isSelectionEnabled"
|
|
26
|
+
:row-number="row.number"
|
|
27
|
+
:seat="seat"
|
|
28
|
+
:selection="getSeatSelection(row.number, seat.column)"
|
|
29
|
+
:style="getSeatDynamicStyle(seat)"
|
|
30
|
+
@select="handleSelectSeat"
|
|
31
|
+
/>
|
|
32
|
+
<template v-if="hasHallway(zone.layout)">
|
|
33
|
+
<span
|
|
34
|
+
v-for="hallway in getHallways(zone.layout)"
|
|
35
|
+
:key="hallway.index"
|
|
36
|
+
class="cpSeatMap__hallway"
|
|
37
|
+
:style="getHallwayDynamicStyle(hallway.index)"
|
|
38
|
+
>
|
|
39
|
+
{{ row.number }}
|
|
40
|
+
</span>
|
|
41
|
+
</template>
|
|
42
|
+
</cp-seat-map-row>
|
|
43
|
+
</cp-seat-map-grid>
|
|
44
|
+
</cp-seat-map-zone>
|
|
45
|
+
</cp-seat-map-plane>
|
|
46
|
+
|
|
47
|
+
<cp-seat-map-emergency-exit-dialog
|
|
48
|
+
v-model:is-visible="isEmergencyExitDialogVisible"
|
|
49
|
+
:config="config"
|
|
50
|
+
@close="handleCloseEmergencyExitDialog"
|
|
51
|
+
@confirm="handleConfirmEmergencyExit"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import { computed, ref } from 'vue'
|
|
58
|
+
|
|
59
|
+
import type { CpSeatMapConfig } from '@/constants/seatMap/CpSeatMapConfig'
|
|
60
|
+
import type { CpSeatMapSeatSelection } from '@/constants/seatMap/CpSeatMapSeatSelection'
|
|
61
|
+
import type { Seat } from '@/constants/seatMap/Seat'
|
|
62
|
+
import type { SeatMap } from '@/constants/seatMap/SeatMap'
|
|
63
|
+
|
|
64
|
+
import { DEFAULT_CP_SEAT_MAP_CONFIG } from '@/constants/seatMap/CpSeatMapConfig'
|
|
65
|
+
import { CpSeatMapTransitionDirections } from '@/constants/seatMap/CpSeatMapTransitionDirections'
|
|
66
|
+
import {
|
|
67
|
+
displaySeat,
|
|
68
|
+
getGridTemplateAreas,
|
|
69
|
+
getGridTemplateColumns,
|
|
70
|
+
getHallways,
|
|
71
|
+
getSeatBlockSize,
|
|
72
|
+
getSeatMapZones,
|
|
73
|
+
getZoneRows,
|
|
74
|
+
hasHallway,
|
|
75
|
+
isLargeSeatMap,
|
|
76
|
+
type SeatMapLayout,
|
|
77
|
+
} from '@/helpers/seatMap'
|
|
78
|
+
import CpSeatMapEmergencyExitDialog from './CpSeatMapEmergencyExitDialog.vue'
|
|
79
|
+
import CpSeatMapGrid from './CpSeatMapGrid.vue'
|
|
80
|
+
import CpSeatMapPlane from './CpSeatMapPlane.vue'
|
|
81
|
+
import CpSeatMapRow from './CpSeatMapRow.vue'
|
|
82
|
+
import CpSeatMapSeatWrapper from './CpSeatMapSeatWrapper.vue'
|
|
83
|
+
import CpSeatMapZone from './CpSeatMapZone.vue'
|
|
84
|
+
|
|
85
|
+
interface Props {
|
|
86
|
+
config?: CpSeatMapConfig
|
|
87
|
+
/** Seat id to pulse — the consumer bumps it to draw the eye to a seat. */
|
|
88
|
+
highlightedSeatId?: number | null
|
|
89
|
+
/** Seats are inert until the consumer has something to seat. */
|
|
90
|
+
isSelectionEnabled?: boolean
|
|
91
|
+
seatMap: SeatMap
|
|
92
|
+
/** Seats the consumer has assigned, keyed as `12A` (see `displaySeat`). */
|
|
93
|
+
selections?: Record<string, CpSeatMapSeatSelection>
|
|
94
|
+
transitionDirection?: CpSeatMapTransitionDirections
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
98
|
+
config: () => DEFAULT_CP_SEAT_MAP_CONFIG,
|
|
99
|
+
highlightedSeatId: null,
|
|
100
|
+
isSelectionEnabled: true,
|
|
101
|
+
selections: () => ({}),
|
|
102
|
+
transitionDirection: CpSeatMapTransitionDirections.FORWARD,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const emit = defineEmits<{ select: [seat: Seat, rowNumber: number] }>()
|
|
106
|
+
|
|
107
|
+
const isEmergencyExitDialogVisible = ref(false)
|
|
108
|
+
const pendingExitRowSeat = ref<{ rowNumber: number; seat: Seat } | null>(null)
|
|
109
|
+
|
|
110
|
+
const zones = computed(() => getSeatMapZones(props.seatMap))
|
|
111
|
+
|
|
112
|
+
const isLargePlane = computed(() => isLargeSeatMap(zones.value))
|
|
113
|
+
|
|
114
|
+
const seatMapClasses = computed(() => {
|
|
115
|
+
return { 'cpSeatMap--isLarge': isLargePlane.value }
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const seatMapDynamicStyle = computed(() => {
|
|
119
|
+
return {
|
|
120
|
+
'--seat-mobile-width': isLargePlane.value ? '32px' : '40px',
|
|
121
|
+
'--hallway-mobile-width': isLargePlane.value ? '16px' : '32px',
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const getGridDynamicStyle = (layout: SeatMapLayout) => {
|
|
126
|
+
return {
|
|
127
|
+
'--columns-count': layout.length,
|
|
128
|
+
'--grid-template-areas': `"${getGridTemplateAreas(layout)}"`,
|
|
129
|
+
'--grid-template-columns': getGridTemplateColumns(layout),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const getSeatSelection = (rowNumber: number, column: string) => {
|
|
134
|
+
return props.selections[displaySeat({ row: rowNumber, column })] ?? null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const getSeatDynamicStyle = (seat: Seat) => {
|
|
138
|
+
return {
|
|
139
|
+
gridColumn: seat.column,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const getHallwayDynamicStyle = (hallwayIndex: number) => {
|
|
144
|
+
return {
|
|
145
|
+
gridArea: `HALLWAY-${hallwayIndex}`,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Exit rows need an acknowledgement before the seat leaves this component; every
|
|
150
|
+
// other seat is emitted straight away. No selection state is held here.
|
|
151
|
+
const handleSelectSeat = (seat: Seat, rowNumber: number) => {
|
|
152
|
+
if (seat.is_exit_row) {
|
|
153
|
+
pendingExitRowSeat.value = { rowNumber, seat }
|
|
154
|
+
isEmergencyExitDialogVisible.value = true
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
emit('select', seat, rowNumber)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const handleCloseEmergencyExitDialog = () => {
|
|
162
|
+
isEmergencyExitDialogVisible.value = false
|
|
163
|
+
pendingExitRowSeat.value = null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const handleConfirmEmergencyExit = () => {
|
|
167
|
+
if (pendingExitRowSeat.value) {
|
|
168
|
+
const { seat, rowNumber } = pendingExitRowSeat.value
|
|
169
|
+
emit('select', seat, rowNumber)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
handleCloseEmergencyExitDialog()
|
|
173
|
+
}
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<style lang="scss">
|
|
177
|
+
.cpSeatMap {
|
|
178
|
+
--hallway-width: 32px;
|
|
179
|
+
--seat-width: 32px;
|
|
180
|
+
|
|
181
|
+
position: relative;
|
|
182
|
+
min-height: 75vh;
|
|
183
|
+
|
|
184
|
+
&__grid > div {
|
|
185
|
+
grid-template-areas: var(--grid-template-areas);
|
|
186
|
+
grid-template-columns: var(--grid-template-columns);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
&__hallway {
|
|
190
|
+
font-size: fn.px-to-em(12);
|
|
191
|
+
font-weight: 700;
|
|
192
|
+
letter-spacing: fn.px-to-rem(2);
|
|
193
|
+
line-height: fn.px-to-rem(24);
|
|
194
|
+
text-transform: uppercase;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
&__row:has(.cpSeatMapRowFacility--isTop) .cpSeatMap__hallway {
|
|
198
|
+
grid-row: 2 !important;
|
|
199
|
+
place-self: end center;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
&__zone {
|
|
203
|
+
position: relative;
|
|
204
|
+
z-index: 1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@include mx.media-query-max(551px) {
|
|
209
|
+
.cpSeatMap {
|
|
210
|
+
--seat-width: var(--seat-mobile-width);
|
|
211
|
+
--hallway-width: var(--hallway-mobile-width);
|
|
212
|
+
|
|
213
|
+
&:not(&--isLarge) &__grid > div {
|
|
214
|
+
gap: var(--cp-spacing-md);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
&__plane .cpSeatMapPlane__plane {
|
|
218
|
+
display: none;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
&__hallway {
|
|
222
|
+
font-size: fn.px-to-em(10);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<cp-dynamic-dialog
|
|
3
|
+
v-model:is-visible="isVisible"
|
|
4
|
+
class="cpSeatMapEmergencyExitDialog"
|
|
5
|
+
:max-width="450"
|
|
6
|
+
:title="config.emergencyExit.title"
|
|
7
|
+
@close="emit('close')"
|
|
8
|
+
>
|
|
9
|
+
<div class="cpSeatMapEmergencyExitDialog__content">
|
|
10
|
+
<p class="cpSeatMapEmergencyExitDialog__description">{{ config.emergencyExit.subtitle }}</p>
|
|
11
|
+
<cp-alert color="warning" :title="config.emergencyExit.disclaimerTitle">
|
|
12
|
+
<p>{{ config.emergencyExit.disclaimerContent }}</p>
|
|
13
|
+
</cp-alert>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<template #footer>
|
|
17
|
+
<div class="cpSeatMapEmergencyExitDialog__footer">
|
|
18
|
+
<cp-button appearance="secondary" class="cpSeatMapEmergencyExitDialog__close" @click="emit('close')">
|
|
19
|
+
{{ config.emergencyExit.close }}
|
|
20
|
+
</cp-button>
|
|
21
|
+
<cp-button color="success" @click="emit('confirm')">
|
|
22
|
+
{{ config.emergencyExit.confirm }}
|
|
23
|
+
</cp-button>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
</cp-dynamic-dialog>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import type { CpSeatMapConfig } from '@/constants/seatMap/CpSeatMapConfig'
|
|
31
|
+
|
|
32
|
+
import { DEFAULT_CP_SEAT_MAP_CONFIG } from '@/constants/seatMap/CpSeatMapConfig'
|
|
33
|
+
import CpAlert from './CpAlert.vue'
|
|
34
|
+
import CpButton from './CpButton.vue'
|
|
35
|
+
import CpDynamicDialog from './CpDynamicDialog.vue'
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
config?: CpSeatMapConfig
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
withDefaults(defineProps<Props>(), {
|
|
42
|
+
config: () => DEFAULT_CP_SEAT_MAP_CONFIG,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits<{
|
|
46
|
+
close: []
|
|
47
|
+
confirm: []
|
|
48
|
+
}>()
|
|
49
|
+
|
|
50
|
+
const isVisible = defineModel<boolean>('isVisible', { default: false })
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<style lang="scss">
|
|
54
|
+
html:has([data-vsbs-backdrop], .cpSeatMapEmergencyExitDialog) [data-vsbs-backdrop] {
|
|
55
|
+
z-index: 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[data-vsbs-sheet]:has(.cpSeatMapEmergencyExitDialog) {
|
|
59
|
+
z-index: 2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.cpSeatMapEmergencyExitDialog {
|
|
63
|
+
&__content {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: var(--cp-spacing-lg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&__description {
|
|
70
|
+
color: var(--cp-text-secondary);
|
|
71
|
+
font-size: var(--cp-text-size-sm);
|
|
72
|
+
line-height: var(--cp-line-height-sm);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&__footer {
|
|
76
|
+
display: flex;
|
|
77
|
+
width: 100%;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: space-between;
|
|
80
|
+
gap: var(--cp-spacing-xl);
|
|
81
|
+
|
|
82
|
+
> * {
|
|
83
|
+
flex: 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpSeatMapGrid">
|
|
3
|
+
<div class="cpSeatMapGrid__columns">
|
|
4
|
+
<span v-for="(column, index) in layout" :key="index" class="cpSeatMapGrid__column">
|
|
5
|
+
{{ column }}
|
|
6
|
+
</span>
|
|
7
|
+
</div>
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import type { SeatMapLayout } from '@/helpers/seatMap'
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
layout: SeatMapLayout
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
defineProps<Props>()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<style lang="scss">
|
|
23
|
+
.cpSeatMapGrid {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
gap: var(--cp-spacing-md);
|
|
29
|
+
|
|
30
|
+
&__columns {
|
|
31
|
+
display: grid;
|
|
32
|
+
font-size: fn.px-to-em(12);
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
gap: var(--cp-spacing-md);
|
|
35
|
+
letter-spacing: fn.px-to-rem(2);
|
|
36
|
+
line-height: fn.px-to-rem(24);
|
|
37
|
+
text-align: center;
|
|
38
|
+
text-transform: uppercase;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpSeatMapPlane" :class="planeDynamicClass">
|
|
3
|
+
<div class="cpSeatMapPlane__plane">
|
|
4
|
+
<icon-plane-nose class="cpSeatMapPlane__nose" />
|
|
5
|
+
<icon-plane-wing class="cpSeatMapPlane__wing cpSeatMapPlane__wing--isLeft" />
|
|
6
|
+
<icon-plane-wing class="cpSeatMapPlane__wing cpSeatMapPlane__wing--isRight" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="cpSeatMapPlane__body">
|
|
10
|
+
<div class="cpSeatMapPlane__cabin">
|
|
11
|
+
<transition mode="out-in" :name="dynamicTransitionName">
|
|
12
|
+
<div :key="mapKey" class="cpSeatMapPlane__content">
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
</transition>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed } from 'vue'
|
|
23
|
+
|
|
24
|
+
import { useIsBreakpoint } from '@/composables/useIsBreakpoint'
|
|
25
|
+
|
|
26
|
+
import { CpSeatMapTransitionDirections } from '@/constants/seatMap/CpSeatMapTransitionDirections'
|
|
27
|
+
import IconPlaneNose from './icons/IconPlaneNose.vue'
|
|
28
|
+
import IconPlaneWing from './icons/IconPlaneWing.vue'
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
isLarge?: boolean
|
|
32
|
+
/** Changing this replays the cabin transition — pass the current segment id. */
|
|
33
|
+
mapKey?: string
|
|
34
|
+
transitionDirection?: CpSeatMapTransitionDirections
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
38
|
+
isLarge: false,
|
|
39
|
+
mapKey: '',
|
|
40
|
+
transitionDirection: CpSeatMapTransitionDirections.FORWARD,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const isMobile = useIsBreakpoint()
|
|
44
|
+
|
|
45
|
+
const planeDynamicClass = computed(() => {
|
|
46
|
+
return {
|
|
47
|
+
'cpSeatMapPlane--isLarge': props.isLarge,
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const dynamicTransitionName = computed(() => {
|
|
52
|
+
if (isMobile.value) return `cpSeatMapSlide-${props.transitionDirection}`
|
|
53
|
+
return 'cpSeatMapFade'
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style lang="scss">
|
|
58
|
+
.cpSeatMapFade-enter-active,
|
|
59
|
+
.cpSeatMapFade-leave-active {
|
|
60
|
+
transition: opacity 300ms ease;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.cpSeatMapFade-enter-from,
|
|
64
|
+
.cpSeatMapFade-leave-to {
|
|
65
|
+
opacity: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.cpSeatMapSlide-backward,
|
|
69
|
+
.cpSeatMapSlide-forward {
|
|
70
|
+
&-enter-active,
|
|
71
|
+
&-leave-active {
|
|
72
|
+
transition:
|
|
73
|
+
transform 200ms ease,
|
|
74
|
+
opacity 200ms ease;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&-enter-from,
|
|
78
|
+
&-leave-to {
|
|
79
|
+
opacity: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&-enter-to {
|
|
83
|
+
transform: translate3d(0, 0, 0);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.cpSeatMapSlide-forward {
|
|
88
|
+
&-enter-from {
|
|
89
|
+
transform: translate3d(3vw, 0, 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&-leave-to {
|
|
93
|
+
transform: translate3d(-3vw, 0, 0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.cpSeatMapSlide-backward {
|
|
98
|
+
&-enter-from {
|
|
99
|
+
transform: translate3d(-3vw, 0, 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&-leave-to {
|
|
103
|
+
transform: translate3d(3vw, 0, 0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.cpSeatMapPlane {
|
|
108
|
+
position: relative;
|
|
109
|
+
height: 100%;
|
|
110
|
+
animation: cp-seat-map-plane-reveal 1000ms ease forwards;
|
|
111
|
+
|
|
112
|
+
$plane-shadow:
|
|
113
|
+
inset 20px 0 20px -20px #eaecff,
|
|
114
|
+
inset -20px 0 20px -20px #eaecff;
|
|
115
|
+
|
|
116
|
+
@keyframes cp-seat-map-plane-reveal {
|
|
117
|
+
from {
|
|
118
|
+
opacity: 0;
|
|
119
|
+
transform: translateY(100%) scale(2);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
to {
|
|
123
|
+
opacity: 1;
|
|
124
|
+
transform: translateY(0) scale(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes cp-seat-map-cabin-reveal {
|
|
129
|
+
to {
|
|
130
|
+
opacity: 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&__nose {
|
|
135
|
+
position: absolute;
|
|
136
|
+
z-index: 0;
|
|
137
|
+
top: 0;
|
|
138
|
+
width: 100%;
|
|
139
|
+
transform: translateY(calc(-100% + fn.px-to-rem(35)));
|
|
140
|
+
|
|
141
|
+
path {
|
|
142
|
+
box-shadow: $plane-shadow;
|
|
143
|
+
stroke-width: fn.px-to-rem(2);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
&__plane {
|
|
148
|
+
position: absolute;
|
|
149
|
+
top: 0;
|
|
150
|
+
left: 0;
|
|
151
|
+
width: 100%;
|
|
152
|
+
height: 100%;
|
|
153
|
+
pointer-events: none;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
&__wing {
|
|
157
|
+
position: absolute;
|
|
158
|
+
top: max(60%, fn.px-to-rem(500));
|
|
159
|
+
width: fn.px-to-rem(180);
|
|
160
|
+
height: max(30%, fn.px-to-rem(265));
|
|
161
|
+
transform: translateY(-60%);
|
|
162
|
+
|
|
163
|
+
&--isLeft {
|
|
164
|
+
left: fn.px-to-rem(-180);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
&--isRight {
|
|
168
|
+
right: fn.px-to-rem(-180);
|
|
169
|
+
transform: translateY(-60%) scaleX(-1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
&__body {
|
|
174
|
+
position: relative;
|
|
175
|
+
z-index: 1;
|
|
176
|
+
min-height: 100%;
|
|
177
|
+
padding: fn.px-to-rem(40) var(--cp-spacing-2xl);
|
|
178
|
+
border-right: 1px solid var(--cp-border-soft);
|
|
179
|
+
border-left: 1px solid var(--cp-border-soft);
|
|
180
|
+
background-color: var(--cp-background-primary);
|
|
181
|
+
box-shadow: $plane-shadow;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
&--isLarge .cpSeatMapPlane__body {
|
|
185
|
+
padding-inline: var(--cp-spacing-md);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
&__cabin {
|
|
189
|
+
animation: cp-seat-map-cabin-reveal 1000ms ease forwards 1000ms;
|
|
190
|
+
opacity: 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
&__content {
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
gap: var(--cp-spacing-2xl);
|
|
197
|
+
transition-timing-function: ease-in-out;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@include mx.media-query-max(551px) {
|
|
202
|
+
.cpSeatMapPlane {
|
|
203
|
+
animation: none;
|
|
204
|
+
|
|
205
|
+
&__body {
|
|
206
|
+
padding: 0;
|
|
207
|
+
border: none;
|
|
208
|
+
background-color: transparent;
|
|
209
|
+
box-shadow: none;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
&__cabin {
|
|
213
|
+
animation-delay: 200ms;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&__content {
|
|
217
|
+
gap: var(--cp-spacing-xl);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
</style>
|