@citizenplane/pimp 18.14.2 → 18.14.3
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/CpButton.vue.d.ts +5 -10
- package/dist/components/CpButton.vue.d.ts.map +1 -1
- package/dist/components/CpSeatMap.vue.d.ts +0 -3
- package/dist/components/CpSeatMap.vue.d.ts.map +1 -1
- package/dist/components/CpSeatMapSeatWrapper.vue.d.ts +1 -1
- package/dist/components/CpSeatMapSeatWrapper.vue.d.ts.map +1 -1
- package/dist/components/CpSwitch.vue.d.ts +0 -1
- package/dist/components/CpSwitch.vue.d.ts.map +1 -1
- package/dist/components/CpToast.vue.d.ts.map +1 -1
- package/dist/pimp.es.js +4709 -4992
- package/dist/pimp.umd.js +28 -28
- package/package.json +2 -3
- package/src/components/CpButton.vue +0 -15
- package/src/components/CpSeatMap.vue +3 -6
- package/src/components/CpSeatMapSeatWrapper.vue +6 -2
- package/src/components/CpSwitch.vue +0 -9
- package/src/components/CpToast.vue +0 -29
- package/src/stories/CpButton.stories.ts +5 -25
- package/src/stories/CpSeatMap.stories.ts +262 -11
- package/src/stories/CpSwitch.stories.ts +0 -22
- package/src/stories/CpToast.stories.ts +0 -3
- package/dist/constants/Hapitcs.d.ts +0 -14
- package/dist/constants/Hapitcs.d.ts.map +0 -1
- package/src/constants/Hapitcs.ts +0 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citizenplane/pimp",
|
|
3
|
-
"version": "18.14.
|
|
3
|
+
"version": "18.14.3",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "storybook dev -p 8081",
|
|
6
6
|
"build-storybook": "storybook build --output-dir ./docs",
|
|
@@ -56,8 +56,7 @@
|
|
|
56
56
|
"primevue": "4.5.5",
|
|
57
57
|
"vue": "3.5.39",
|
|
58
58
|
"vue-bind-once": "0.2.1",
|
|
59
|
-
"vue-tel-input": "9.5.0"
|
|
60
|
-
"web-haptics": "0.0.6"
|
|
59
|
+
"vue-tel-input": "9.5.0"
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
62
|
"@biomejs/biome": "2.5.5",
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
role="button"
|
|
11
11
|
tabindex="0"
|
|
12
12
|
:type="type"
|
|
13
|
-
@click="handleClick"
|
|
14
13
|
>
|
|
15
14
|
<span class="cpButton__body">
|
|
16
15
|
<span v-if="isLoading" class="cpButton__loader">
|
|
@@ -32,18 +31,15 @@
|
|
|
32
31
|
<script setup lang="ts">
|
|
33
32
|
import type { Slots } from 'vue'
|
|
34
33
|
import { computed, useSlots } from 'vue'
|
|
35
|
-
import { useWebHaptics } from 'web-haptics/vue'
|
|
36
34
|
|
|
37
35
|
import CpLoader from '@/components/CpLoader.vue'
|
|
38
36
|
|
|
39
|
-
import { Haptics } from '@/constants/Hapitcs'
|
|
40
37
|
import { capitalizeFirstLetter } from '@/helpers'
|
|
41
38
|
|
|
42
39
|
export interface Props {
|
|
43
40
|
appearance?: 'primary' | 'secondary' | 'tertiary'
|
|
44
41
|
color?: 'neutral' | 'accent' | 'error' | 'warning' | 'success'
|
|
45
42
|
disabled?: boolean
|
|
46
|
-
enableHaptics?: boolean
|
|
47
43
|
href?: string
|
|
48
44
|
isLoading?: boolean
|
|
49
45
|
isSquare?: boolean
|
|
@@ -61,12 +57,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
61
57
|
size: 'md',
|
|
62
58
|
})
|
|
63
59
|
|
|
64
|
-
const emit = defineEmits<{
|
|
65
|
-
click: [event: MouseEvent]
|
|
66
|
-
}>()
|
|
67
|
-
|
|
68
60
|
const slots: Slots = useSlots()
|
|
69
|
-
const { trigger } = useWebHaptics()
|
|
70
61
|
|
|
71
62
|
const capitalizedAppearance = computed(() => capitalizeFirstLetter(props.appearance))
|
|
72
63
|
|
|
@@ -96,12 +87,6 @@ const dynamicClasses = computed(() => {
|
|
|
96
87
|
]
|
|
97
88
|
})
|
|
98
89
|
|
|
99
|
-
const handleClick = (event: MouseEvent) => {
|
|
100
|
-
if (props.enableHaptics) {
|
|
101
|
-
trigger(Haptics.RIGID)
|
|
102
|
-
}
|
|
103
|
-
emit('click', event)
|
|
104
|
-
}
|
|
105
90
|
</script>
|
|
106
91
|
|
|
107
92
|
<style lang="scss">
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
v-for="seat in row.seats"
|
|
22
22
|
:key="seat.id"
|
|
23
23
|
:config="config"
|
|
24
|
-
:is-highlighted="
|
|
24
|
+
:is-highlighted="isSeatHighlighted(seat)"
|
|
25
25
|
:is-selection-enabled="isSelectionEnabled"
|
|
26
26
|
:row-number="row.number"
|
|
27
27
|
:seat="seat"
|
|
@@ -84,12 +84,9 @@ import CpSeatMapZone from './CpSeatMapZone.vue'
|
|
|
84
84
|
|
|
85
85
|
interface Props {
|
|
86
86
|
config?: CpSeatMapConfig
|
|
87
|
-
/** Seat id to pulse — the consumer bumps it to draw the eye to a seat. */
|
|
88
87
|
highlightedSeatId?: number | null
|
|
89
|
-
/** Seats are inert until the consumer has something to seat. */
|
|
90
88
|
isSelectionEnabled?: boolean
|
|
91
89
|
seatMap: SeatMap
|
|
92
|
-
/** Seats the consumer has assigned, keyed as `12A` (see `displaySeat`). */
|
|
93
90
|
selections?: Record<string, CpSeatMapSeatSelection>
|
|
94
91
|
transitionDirection?: CpSeatMapTransitionDirections
|
|
95
92
|
}
|
|
@@ -146,8 +143,8 @@ const getHallwayDynamicStyle = (hallwayIndex: number) => {
|
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
const isSeatHighlighted = (seat: Seat) => props.highlightedSeatId === seat.id
|
|
147
|
+
|
|
151
148
|
const handleSelectSeat = (seat: Seat, rowNumber: number) => {
|
|
152
149
|
if (seat.is_exit_row) {
|
|
153
150
|
pendingExitRowSeat.value = { rowNumber, seat }
|
|
@@ -69,10 +69,14 @@ const isSeatUnavailable = computed(() => isSeatBlocked.value || isSeatOccupied.v
|
|
|
69
69
|
const isSeatPayable = computed(() => props.seat.status === SeatStatuses.PAYABLE)
|
|
70
70
|
|
|
71
71
|
const highlightSeat = () => {
|
|
72
|
-
seatWrapperRef.value
|
|
72
|
+
if (!seatWrapperRef.value) return
|
|
73
|
+
|
|
74
|
+
seatWrapperRef.value.classList.add('cpSeatMapSeatWrapper--isHighlighted')
|
|
75
|
+
seatWrapperRef.value.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
73
76
|
|
|
74
77
|
setTimeout(() => {
|
|
75
|
-
seatWrapperRef.value
|
|
78
|
+
if (!seatWrapperRef.value) return
|
|
79
|
+
seatWrapperRef.value.classList.remove('cpSeatMapSeatWrapper--isHighlighted')
|
|
76
80
|
}, HIGHLIGHT_DURATION)
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -30,19 +30,16 @@
|
|
|
30
30
|
|
|
31
31
|
<script setup lang="ts">
|
|
32
32
|
import { computed, useId } from 'vue'
|
|
33
|
-
import { useWebHaptics } from 'web-haptics/vue'
|
|
34
33
|
|
|
35
34
|
import CpTooltip from '@/components/CpTooltip.vue'
|
|
36
35
|
|
|
37
36
|
import { ToggleColors } from '@/constants'
|
|
38
|
-
import { Haptics } from '@/constants/Hapitcs'
|
|
39
37
|
import { capitalizeFirstLetter } from '@/helpers'
|
|
40
38
|
|
|
41
39
|
interface Props {
|
|
42
40
|
autofocus?: boolean
|
|
43
41
|
color?: ToggleColors
|
|
44
42
|
disabled?: boolean
|
|
45
|
-
enableHaptics?: boolean
|
|
46
43
|
groupName?: string
|
|
47
44
|
helper?: string
|
|
48
45
|
inert?: boolean
|
|
@@ -73,8 +70,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
73
70
|
|
|
74
71
|
const emit = defineEmits<Emits>()
|
|
75
72
|
|
|
76
|
-
const { trigger } = useWebHaptics()
|
|
77
|
-
|
|
78
73
|
const switchUniqueId = useId()
|
|
79
74
|
|
|
80
75
|
const capitalizedColor = computed(() => {
|
|
@@ -104,10 +99,6 @@ const computedClasses = computed(() => {
|
|
|
104
99
|
})
|
|
105
100
|
|
|
106
101
|
const handleClick = (value: boolean): void => {
|
|
107
|
-
if (props.enableHaptics) {
|
|
108
|
-
trigger(Haptics.SOFT)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
102
|
emit('update:modelValue', !value)
|
|
112
103
|
}
|
|
113
104
|
</script>
|
|
@@ -62,17 +62,12 @@
|
|
|
62
62
|
|
|
63
63
|
<script setup lang="ts">
|
|
64
64
|
import Toast from 'primevue/toast'
|
|
65
|
-
import ToastEventBus from 'primevue/toasteventbus'
|
|
66
|
-
import { onMounted } from 'vue'
|
|
67
|
-
import { useWebHaptics } from 'web-haptics/vue'
|
|
68
65
|
|
|
69
66
|
import { CpToastTypes } from '@/constants/CpToastTypes'
|
|
70
|
-
import { Haptics } from '@/constants/Hapitcs'
|
|
71
67
|
import { capitalizeFirstLetter } from '@/helpers'
|
|
72
68
|
|
|
73
69
|
interface Message {
|
|
74
70
|
detail?: string
|
|
75
|
-
enableHaptics?: boolean
|
|
76
71
|
life?: number
|
|
77
72
|
primaryAction?: {
|
|
78
73
|
label: string
|
|
@@ -91,8 +86,6 @@ interface Message {
|
|
|
91
86
|
summary: string
|
|
92
87
|
}
|
|
93
88
|
|
|
94
|
-
onMounted(() => ToastEventBus.on('add', (payload: unknown) => handleToastAdded(payload as Message)))
|
|
95
|
-
|
|
96
89
|
const displayTimer = (message: Message) => message.showTimer && message.life !== undefined
|
|
97
90
|
|
|
98
91
|
const getDynamicClass = (severity: string) => `cpToast--is${capitalizeFirstLetter(severity)}`
|
|
@@ -135,28 +128,6 @@ const handleActionClick = (onClick: VoidFunction, closeCallback: VoidFunction) =
|
|
|
135
128
|
}
|
|
136
129
|
|
|
137
130
|
const handleMouse = () => true
|
|
138
|
-
|
|
139
|
-
const { trigger } = useWebHaptics()
|
|
140
|
-
|
|
141
|
-
const resolveHapticLevel = (severity: CpToastTypes) => {
|
|
142
|
-
switch (severity) {
|
|
143
|
-
case CpToastTypes.SUCCESS:
|
|
144
|
-
return Haptics.SUCCESS
|
|
145
|
-
case CpToastTypes.WARNING:
|
|
146
|
-
return Haptics.WARNING
|
|
147
|
-
case CpToastTypes.ERROR:
|
|
148
|
-
return Haptics.ERROR
|
|
149
|
-
case CpToastTypes.INFO:
|
|
150
|
-
default:
|
|
151
|
-
return Haptics.SOFT
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const handleToastAdded = (message: Message) => {
|
|
156
|
-
if (message.enableHaptics) {
|
|
157
|
-
trigger(resolveHapticLevel(message.severity))
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
131
|
</script>
|
|
161
132
|
|
|
162
133
|
<style lang="scss">
|
|
@@ -50,10 +50,6 @@ const meta = {
|
|
|
50
50
|
control: 'boolean',
|
|
51
51
|
description: 'Whether the button has square corners',
|
|
52
52
|
},
|
|
53
|
-
enableHaptics: {
|
|
54
|
-
control: 'boolean',
|
|
55
|
-
description: 'Trigger haptic feedback on click (supported devices only)',
|
|
56
|
-
},
|
|
57
53
|
},
|
|
58
54
|
} satisfies Meta<typeof CpButton>
|
|
59
55
|
|
|
@@ -61,12 +57,15 @@ export default meta
|
|
|
61
57
|
|
|
62
58
|
type Story = StoryObj<typeof meta>
|
|
63
59
|
|
|
64
|
-
const defaultTemplate = '<CpButton v-bind="args">Button</CpButton>'
|
|
60
|
+
const defaultTemplate = '<CpButton v-bind="args" @click="handleClick">Button</CpButton>'
|
|
65
61
|
|
|
66
62
|
const defaultRender = (args: Args) => ({
|
|
67
63
|
components: { CpButton },
|
|
68
64
|
setup() {
|
|
69
|
-
|
|
65
|
+
const handleClick = (event: MouseEvent) => {
|
|
66
|
+
console.log('Button clicked', event)
|
|
67
|
+
}
|
|
68
|
+
return { args, handleClick }
|
|
70
69
|
},
|
|
71
70
|
template: defaultTemplate,
|
|
72
71
|
})
|
|
@@ -338,22 +337,3 @@ export const ResetType: Story = {
|
|
|
338
337
|
`,
|
|
339
338
|
}),
|
|
340
339
|
}
|
|
341
|
-
|
|
342
|
-
/* -------------------------------------------------------------------------- */
|
|
343
|
-
/* Interaction */
|
|
344
|
-
/* -------------------------------------------------------------------------- */
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Triggers a haptic feedback on supported devices on click.
|
|
348
|
-
*/
|
|
349
|
-
export const WithHaptics: Story = {
|
|
350
|
-
args: { ...Default.args, enableHaptics: true, color: 'accent' },
|
|
351
|
-
render: (args: Args) => ({
|
|
352
|
-
components: { CpButton },
|
|
353
|
-
setup() {
|
|
354
|
-
const onClick = () => console.log('Button clicked')
|
|
355
|
-
return { args, onClick }
|
|
356
|
-
},
|
|
357
|
-
template: `<CpButton v-bind="args" @click="onClick">Button with haptics</CpButton>`,
|
|
358
|
-
}),
|
|
359
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
2
2
|
import { action } from 'storybook/actions'
|
|
3
|
-
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import { computed, nextTick, ref, watch } from 'vue'
|
|
4
4
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
|
5
5
|
|
|
6
6
|
import type { CpSeatMapSeatSelection } from '@/constants/seatMap/CpSeatMapSeatSelection'
|
|
@@ -10,6 +10,8 @@ import type { SeatMapZone } from '@/constants/seatMap/SeatMapZone'
|
|
|
10
10
|
import type { SeatRow } from '@/constants/seatMap/SeatRow'
|
|
11
11
|
import type { SeatRowFacilityFlags } from '@/constants/seatMap/SeatRowFacilityFlags'
|
|
12
12
|
|
|
13
|
+
import { useIsBreakpoint } from '@/composables/useIsBreakpoint'
|
|
14
|
+
|
|
13
15
|
import CpSeatMap from '@/components/CpSeatMap.vue'
|
|
14
16
|
|
|
15
17
|
import { IataTravelerTypes } from '@/constants/seatMap/IataTravelerTypes'
|
|
@@ -50,11 +52,27 @@ const meta = {
|
|
|
50
52
|
},
|
|
51
53
|
seatMap: { control: false, table: { disable: true } },
|
|
52
54
|
},
|
|
53
|
-
// The plane wings sit 180px outside the cabin, so the story needs side room.
|
|
55
|
+
// The plane wings sit 180px outside the cabin, so the story needs side room. The
|
|
56
|
+
// grey backdrop is what makes the white fuselage and wings readable; mobile hides
|
|
57
|
+
// the plane altogether, so it goes back to a plain background there.
|
|
54
58
|
decorators: [
|
|
55
59
|
() => ({
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
setup() {
|
|
61
|
+
const isMobile = useIsBreakpoint()
|
|
62
|
+
|
|
63
|
+
const backdropStyle = computed(() => ({
|
|
64
|
+
display: 'flex',
|
|
65
|
+
margin: '0 auto',
|
|
66
|
+
justifyContent: 'center',
|
|
67
|
+
padding: '40px',
|
|
68
|
+
borderRadius: '8px',
|
|
69
|
+
backgroundColor: isMobile.value ? 'transparent' : 'var(--cp-background-tertiary)',
|
|
70
|
+
overflow: 'hidden',
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
return { backdropStyle }
|
|
74
|
+
},
|
|
75
|
+
template: '<div :style="backdropStyle"><story/></div>',
|
|
58
76
|
}),
|
|
59
77
|
],
|
|
60
78
|
} satisfies Meta<CpSeatMapArgs>
|
|
@@ -342,19 +360,252 @@ export const Default: Story = {
|
|
|
342
360
|
render: withAircraftControl(true),
|
|
343
361
|
}
|
|
344
362
|
|
|
363
|
+
interface StoryTraveler {
|
|
364
|
+
id: number
|
|
365
|
+
initials: string
|
|
366
|
+
name: string
|
|
367
|
+
/** Seat label such as `7B`, `null` while the traveler has no seat. */
|
|
368
|
+
seat: null | string
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Rebuilt on demand: switching aircraft has to start the seating over, and `7B`
|
|
373
|
+
* and `8C` are the two seats every map in this file keeps free.
|
|
374
|
+
*/
|
|
375
|
+
const makeTravelers = (): StoryTraveler[] => [
|
|
376
|
+
{ id: 1, initials: 'JD', name: 'Jane Doe', seat: '7B' },
|
|
377
|
+
{ id: 2, initials: 'AM', name: 'Alex Moore', seat: '8C' },
|
|
378
|
+
{ id: 3, initials: 'SR', name: 'Sam Reed', seat: null },
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
const travelerSelections = (travelers: StoryTraveler[]) => {
|
|
382
|
+
return travelers.reduce<Record<string, CpSeatMapSeatSelection>>((accumulator, traveler) => {
|
|
383
|
+
if (traveler.seat) accumulator[traveler.seat] = { initials: traveler.initials }
|
|
384
|
+
return accumulator
|
|
385
|
+
}, {})
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const getLayoutStyle = (isMobile: boolean) => ({
|
|
389
|
+
display: 'flex',
|
|
390
|
+
width: '100%',
|
|
391
|
+
justifyContent: 'center',
|
|
392
|
+
// The panel floats over the bottom of the screen, the last rows would sit under it.
|
|
393
|
+
paddingBottom: isMobile ? '180px' : '0',
|
|
394
|
+
})
|
|
395
|
+
|
|
345
396
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
397
|
+
* Fixed on both sizes: it follows the scroll and stays out of the flow, so the plane
|
|
398
|
+
* keeps the middle of the story. Mobile pins it to the bottom of the screen, desktop
|
|
399
|
+
* to the right-hand side.
|
|
400
|
+
*/
|
|
401
|
+
const getPanelStyle = (isMobile: boolean) => ({
|
|
402
|
+
position: 'fixed',
|
|
403
|
+
zIndex: 2,
|
|
404
|
+
...(isMobile
|
|
405
|
+
? { right: 'var(--cp-spacing-lg)', bottom: 'var(--cp-spacing-lg)', left: 'var(--cp-spacing-lg)' }
|
|
406
|
+
: { top: 'var(--cp-spacing-2xl)', right: 'var(--cp-spacing-2xl)', width: '232px' }),
|
|
407
|
+
display: 'flex',
|
|
408
|
+
flexDirection: 'column',
|
|
409
|
+
gap: 'var(--cp-spacing-xs)',
|
|
410
|
+
padding: isMobile ? 'var(--cp-spacing-md)' : 'var(--cp-spacing-lg)',
|
|
411
|
+
border: '1px solid var(--cp-border-soft)',
|
|
412
|
+
borderRadius: 'var(--cp-radius-lg)',
|
|
413
|
+
backgroundColor: 'var(--cp-background-primary)',
|
|
414
|
+
boxShadow: 'var(--cp-shadows-overlay)',
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* The seating, the active traveler and the seat to highlight all live in the
|
|
419
|
+
* story: the map reports a click and renders `selections` and `highlightedSeatId`,
|
|
420
|
+
* it remembers nothing. `isSelectionEnabled` follows the active traveler, so the
|
|
421
|
+
* seats stay disabled — and tooltipped "Select a traveler first" — until one is
|
|
422
|
+
* picked.
|
|
423
|
+
*/
|
|
424
|
+
const withTravelerPanel = (args: CpSeatMapArgs) => ({
|
|
425
|
+
components: { CpSeatMap },
|
|
426
|
+
setup() {
|
|
427
|
+
const seatMap = computed(() => AIRCRAFT[args.aircraft])
|
|
428
|
+
|
|
429
|
+
const isMobile = useIsBreakpoint()
|
|
430
|
+
|
|
431
|
+
const travelers = ref(makeTravelers())
|
|
432
|
+
const activeTravelerId = ref<null | number>(null)
|
|
433
|
+
const highlightedSeatId = ref<null | number>(null)
|
|
434
|
+
|
|
435
|
+
const componentArgs = computed(() => {
|
|
436
|
+
const { aircraft, seatMap: _, ...rest } = args
|
|
437
|
+
return rest
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
const selections = computed(() => travelerSelections(travelers.value))
|
|
441
|
+
|
|
442
|
+
const activeTraveler = computed(() => {
|
|
443
|
+
return travelers.value.find((traveler) => traveler.id === activeTravelerId.value) ?? null
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
// The panel works in seat labels, `highlightedSeatId` expects the seat id.
|
|
447
|
+
const seatIds = computed(() => {
|
|
448
|
+
const entries = seatMap.value.rows.flatMap((row) =>
|
|
449
|
+
Object.values(row.seats).map(
|
|
450
|
+
(seat) => [displaySeat({ row: row.number, column: seat.column }), seat.id] as const,
|
|
451
|
+
),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return new Map(entries)
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
watch(
|
|
458
|
+
() => args.aircraft,
|
|
459
|
+
() => {
|
|
460
|
+
travelers.value = makeTravelers()
|
|
461
|
+
activeTravelerId.value = null
|
|
462
|
+
highlightedSeatId.value = null
|
|
463
|
+
},
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
/** The map only plays the highlight when the seat flips to highlighted, hence the reset. */
|
|
467
|
+
const highlight = async (seatLabel: null | string) => {
|
|
468
|
+
highlightedSeatId.value = null
|
|
469
|
+
await nextTick()
|
|
470
|
+
highlightedSeatId.value = seatLabel ? (seatIds.value.get(seatLabel) ?? null) : null
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const handleSelectTraveler = (traveler: StoryTraveler) => {
|
|
474
|
+
activeTravelerId.value = traveler.id
|
|
475
|
+
highlight(traveler.seat)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const handleSelect = (seat: Seat, rowNumber: number) => {
|
|
479
|
+
action('select')(seat, rowNumber)
|
|
480
|
+
|
|
481
|
+
const traveler = activeTraveler.value
|
|
482
|
+
if (!traveler) return
|
|
483
|
+
|
|
484
|
+
const seatLabel = displaySeat({ row: rowNumber, column: seat.column })
|
|
485
|
+
const occupant = travelers.value.find((other) => other.seat === seatLabel)
|
|
486
|
+
|
|
487
|
+
// Their own seat frees it, a seat held by another traveler swaps the two.
|
|
488
|
+
if (occupant === traveler) {
|
|
489
|
+
traveler.seat = null
|
|
490
|
+
} else {
|
|
491
|
+
if (occupant) occupant.seat = traveler.seat
|
|
492
|
+
traveler.seat = seatLabel
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
highlight(traveler.seat)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const hint = computed(() => {
|
|
499
|
+
if (!activeTraveler.value) return 'Select a traveler to assign a seat.'
|
|
500
|
+
return `Pick a seat for ${activeTraveler.value.name}.`
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
const travelerStyle = (traveler: StoryTraveler) => {
|
|
504
|
+
const isActive = traveler.id === activeTravelerId.value
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
display: 'flex',
|
|
508
|
+
width: '100%',
|
|
509
|
+
alignItems: 'center',
|
|
510
|
+
gap: 'var(--cp-spacing-sm)',
|
|
511
|
+
padding: 'var(--cp-spacing-sm)',
|
|
512
|
+
border: `1px solid ${isActive ? 'var(--cp-border-accent-primary)' : 'transparent'}`,
|
|
513
|
+
borderRadius: 'var(--cp-radius-md)',
|
|
514
|
+
backgroundColor: isActive ? 'var(--cp-background-accent-primary)' : 'transparent',
|
|
515
|
+
cursor: 'pointer',
|
|
516
|
+
textAlign: 'left',
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const initialsStyle = (traveler: StoryTraveler) => ({
|
|
521
|
+
display: 'flex',
|
|
522
|
+
width: '28px',
|
|
523
|
+
height: '28px',
|
|
524
|
+
flex: '0 0 auto',
|
|
525
|
+
alignItems: 'center',
|
|
526
|
+
justifyContent: 'center',
|
|
527
|
+
borderRadius: 'var(--cp-radius-full)',
|
|
528
|
+
backgroundColor: traveler.seat ? 'var(--cp-background-accent-solid)' : 'var(--cp-background-secondary)',
|
|
529
|
+
color: traveler.seat ? 'var(--cp-text-white)' : 'var(--cp-text-tertiary)',
|
|
530
|
+
fontSize: '11px',
|
|
531
|
+
fontWeight: '700',
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
const seatStyle = (traveler: StoryTraveler) => ({
|
|
535
|
+
marginLeft: 'auto',
|
|
536
|
+
color: traveler.seat ? 'var(--cp-text-primary)' : 'var(--cp-text-tertiary)',
|
|
537
|
+
fontWeight: '600',
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
const layoutStyle = computed(() => getLayoutStyle(isMobile.value))
|
|
541
|
+
const panelStyle = computed(() => getPanelStyle(isMobile.value))
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
activeTravelerId,
|
|
545
|
+
componentArgs,
|
|
546
|
+
handleSelect,
|
|
547
|
+
handleSelectTraveler,
|
|
548
|
+
highlightedSeatId,
|
|
549
|
+
hint,
|
|
550
|
+
initialsStyle,
|
|
551
|
+
isMobile,
|
|
552
|
+
layoutStyle,
|
|
553
|
+
panelStyle,
|
|
554
|
+
seatMap,
|
|
555
|
+
seatStyle,
|
|
556
|
+
selections,
|
|
557
|
+
travelerStyle,
|
|
558
|
+
travelers,
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
template: `
|
|
562
|
+
<div :style="layoutStyle">
|
|
563
|
+
<cp-seat-map
|
|
564
|
+
v-bind="componentArgs"
|
|
565
|
+
:highlighted-seat-id="highlightedSeatId"
|
|
566
|
+
:is-selection-enabled="activeTravelerId !== null"
|
|
567
|
+
:seat-map="seatMap"
|
|
568
|
+
:selections="selections"
|
|
569
|
+
@select="handleSelect"
|
|
570
|
+
/>
|
|
571
|
+
|
|
572
|
+
<aside :style="panelStyle">
|
|
573
|
+
<p
|
|
574
|
+
v-if="!isMobile"
|
|
575
|
+
style="margin: 0 0 var(--cp-spacing-sm); color: var(--cp-text-secondary); font-size: 12px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase;"
|
|
576
|
+
>
|
|
577
|
+
Travelers
|
|
578
|
+
</p>
|
|
579
|
+
<button
|
|
580
|
+
v-for="traveler in travelers"
|
|
581
|
+
:key="traveler.id"
|
|
582
|
+
:style="travelerStyle(traveler)"
|
|
583
|
+
type="button"
|
|
584
|
+
@click="handleSelectTraveler(traveler)"
|
|
585
|
+
>
|
|
586
|
+
<span :style="initialsStyle(traveler)">{{ traveler.initials }}</span>
|
|
587
|
+
<span style="font-size: 13px;">{{ traveler.name }}</span>
|
|
588
|
+
<span :style="seatStyle(traveler)">{{ traveler.seat || '—' }}</span>
|
|
589
|
+
</button>
|
|
590
|
+
<p style="margin: var(--cp-spacing-sm) 0 0; color: var(--cp-text-tertiary); font-size: 12px;">{{ hint }}</p>
|
|
591
|
+
</aside>
|
|
592
|
+
</div>
|
|
593
|
+
`,
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Three travelers, two of them already seated, listed in a panel that leaves the
|
|
598
|
+
* plane centered and follows the scroll — pinned to the right of the screen on
|
|
599
|
+
* desktop, to the bottom of it on mobile. Clicking one makes it the
|
|
600
|
+
* active traveler, scrolls its seat into view and highlights it; clicking a seat then
|
|
601
|
+
* moves that traveler over, clicking its own seat frees it, and clicking a seat
|
|
602
|
+
* another traveler holds swaps the two.
|
|
348
603
|
*/
|
|
349
604
|
export const WithSelection: Story = {
|
|
350
605
|
args: {
|
|
351
|
-
selections: {
|
|
352
|
-
...selection(7, 'B', { initials: 'JD' }),
|
|
353
|
-
...selection(8, 'C', { initials: 'AM' }),
|
|
354
|
-
},
|
|
355
606
|
onSelect: action('select'),
|
|
356
607
|
},
|
|
357
|
-
render:
|
|
608
|
+
render: withTravelerPanel,
|
|
358
609
|
}
|
|
359
610
|
|
|
360
611
|
/**
|
|
@@ -245,28 +245,6 @@ export const HelpAndTooltip: Story = {
|
|
|
245
245
|
}),
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
/**
|
|
249
|
-
* Triggers a haptic feedback on supported devices when toggled.
|
|
250
|
-
*/
|
|
251
|
-
export const WithHaptics: Story = {
|
|
252
|
-
args: {
|
|
253
|
-
...Default.args,
|
|
254
|
-
enableHaptics: true,
|
|
255
|
-
},
|
|
256
|
-
render: (args) => ({
|
|
257
|
-
components: { CpSwitch },
|
|
258
|
-
setup() {
|
|
259
|
-
const value = ref(false)
|
|
260
|
-
return { args, value }
|
|
261
|
-
},
|
|
262
|
-
template: `
|
|
263
|
-
<div style="padding: 20px;">
|
|
264
|
-
<CpSwitch v-model="value" v-bind="args" />
|
|
265
|
-
</div>
|
|
266
|
-
`,
|
|
267
|
-
}),
|
|
268
|
-
}
|
|
269
|
-
|
|
270
248
|
/* -------------------------------------------------------------------------- */
|
|
271
249
|
/* Group */
|
|
272
250
|
/* -------------------------------------------------------------------------- */
|
|
@@ -17,7 +17,6 @@ type CpToastPosition =
|
|
|
17
17
|
|
|
18
18
|
/** `CpToast` has no `defineProps`; layout props are passed through to PrimeVue `Toast`. */
|
|
19
19
|
type CpToastStoryArgs = {
|
|
20
|
-
enableHaptics?: boolean
|
|
21
20
|
group?: string
|
|
22
21
|
position?: CpToastPosition
|
|
23
22
|
}
|
|
@@ -45,7 +44,6 @@ const meta = {
|
|
|
45
44
|
args: {
|
|
46
45
|
position: 'top-right',
|
|
47
46
|
group: undefined,
|
|
48
|
-
enableHaptics: false,
|
|
49
47
|
},
|
|
50
48
|
} satisfies Meta<CpToastStoryArgs>
|
|
51
49
|
|
|
@@ -67,7 +65,6 @@ export const Severities: Story = {
|
|
|
67
65
|
severity: CpToastTypes.SECONDARY,
|
|
68
66
|
summary: "Hello i'm a cpToast!",
|
|
69
67
|
detail: 'This is a cpToast description.',
|
|
70
|
-
enableHaptics: true,
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
const notify = (severity: string) => toast.add({ ...baseOptions, severity })
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export declare const Haptics: {
|
|
2
|
-
SUCCESS: string;
|
|
3
|
-
WARNING: string;
|
|
4
|
-
ERROR: string;
|
|
5
|
-
LIGHT: string;
|
|
6
|
-
MEDIUM: string;
|
|
7
|
-
HEAVY: string;
|
|
8
|
-
SOFT: string;
|
|
9
|
-
RIGID: string;
|
|
10
|
-
SELECTION: string;
|
|
11
|
-
NUDGE: string;
|
|
12
|
-
BUZZ: string;
|
|
13
|
-
};
|
|
14
|
-
//# sourceMappingURL=Hapitcs.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Hapitcs.d.ts","sourceRoot":"","sources":["../../src/constants/Hapitcs.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO;;;;;;;;;;;;CAYnB,CAAA"}
|
package/src/constants/Hapitcs.ts
DELETED