@citizenplane/pimp 8.25.5 → 8.26.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/{IconAccompaniedMinorEach-Cx5Bfcmr.js → IconAccompaniedMinorEach-CUq3oXbX.js} +1 -1
- package/dist/{IconAccompaniedMinorNone-BQEYAxoo.js → IconAccompaniedMinorNone-nZ4eSfTj.js} +1 -1
- package/dist/{IconAccompaniedMinorOne-Bi2kXTM4.js → IconAccompaniedMinorOne-CDMqq14b.js} +1 -1
- package/dist/{IconAddReceipt-BO0fhfpX.js → IconAddReceipt-DRpGiWBU.js} +1 -1
- package/dist/{IconAirportTerminal-CnADY0bO.js → IconAirportTerminal-8k-3lKjb.js} +1 -1
- package/dist/{IconArrival-DxunnDcP.js → IconArrival-m6HnOVje.js} +1 -1
- package/dist/{IconBroadcast-eYqgZYdX.js → IconBroadcast-B13UworG.js} +1 -1
- package/dist/{IconCabinBag-DBjCF2Xv.js → IconCabinBag-CNnzHz2B.js} +1 -1
- package/dist/{IconCheckedBaggage-Cc_Vcylv.js → IconCheckedBaggage-CkxUDHe9.js} +1 -1
- package/dist/{IconCheckedBaggage20-BK8BcWZs.js → IconCheckedBaggage20-DEtphLSa.js} +1 -1
- package/dist/{IconCheckedBaggage30-BiZ7g8Aj.js → IconCheckedBaggage30-CmAddx5h.js} +1 -1
- package/dist/{IconChild-zmKeUrtL.js → IconChild-OReHyUco.js} +1 -1
- package/dist/{IconContact-B_vCsT11.js → IconContact-D2N23RZ5.js} +1 -1
- package/dist/{IconDeparture-CvB1tCf8.js → IconDeparture-D10LaXRX.js} +1 -1
- package/dist/{IconDistribution-OwUgE77-.js → IconDistribution-SpPiru9I.js} +1 -1
- package/dist/{IconDistributionClosed-KSOCX-B1.js → IconDistributionClosed-Bzqe7nju.js} +1 -1
- package/dist/{IconDistributionExclusivePair-D8NB9AEy.js → IconDistributionExclusivePair-CjPM-_R1.js} +1 -1
- package/dist/{IconDistributionSided-CL1YtqJY.js → IconDistributionSided-DehjCN0D.js} +1 -1
- package/dist/{IconDistributionSupplySided-BTo3QGcS.js → IconDistributionSupplySided-DWCyXqd1.js} +1 -1
- package/dist/{IconDynamicContent-DDYXTrt5.js → IconDynamicContent-BvzbgXvW.js} +1 -1
- package/dist/{IconFares-w-fch-MB.js → IconFares-zARDpPNl.js} +1 -1
- package/dist/{IconFaresOutlined-2munwzp-.js → IconFaresOutlined-DLFV8nwg.js} +1 -1
- package/dist/{IconFemale-DIK-uMjs.js → IconFemale-Ba4uoI-S.js} +1 -1
- package/dist/{IconFindConversation-BFBdGFw_.js → IconFindConversation-d0pP3wG9.js} +1 -1
- package/dist/{IconFire-CJP4meA_.js → IconFire-CXzWKoMB.js} +1 -1
- package/dist/{IconFlight-BSQIWsWY.js → IconFlight-Cof8M5dO.js} +1 -1
- package/dist/{IconFlightReturn-DfOxod0d.js → IconFlightReturn-CA9iGMcW.js} +1 -1
- package/dist/{IconHandHeart-CPX-j2Eo.js → IconHandHeart-CCLKnMOm.js} +1 -1
- package/dist/{IconHistory-DAkQZP1Y.js → IconHistory-DI6WD_3J.js} +1 -1
- package/dist/{IconHourGlass-BVk7qcq5.js → IconHourGlass-BorNLEca.js} +1 -1
- package/dist/{IconIdCard-tjli750D.js → IconIdCard-DhbhBkul.js} +1 -1
- package/dist/{IconInfant-CyTuBv9E.js → IconInfant-D4EztT9g.js} +1 -1
- package/dist/{IconItinerary-BkgXDYJv.js → IconItinerary-Bhj_lgG2.js} +1 -1
- package/dist/{IconLeave-BdqJt7vt.js → IconLeave-BvpY7gdD.js} +1 -1
- package/dist/{IconMale-DpMX6cGS.js → IconMale-RMd_9ZSg.js} +1 -1
- package/dist/{IconMultiSegments-DgoJo4Jp.js → IconMultiSegments-DROUj0t5.js} +1 -1
- package/dist/{IconNoPassport-DkIgwNpl.js → IconNoPassport-DBmaQH_g.js} +1 -1
- package/dist/{IconNoRefund-ChebOxWH.js → IconNoRefund-yNAZr7uX.js} +1 -1
- package/dist/{IconNotion-CGMc2WxX.js → IconNotion-CpZhGILz.js} +1 -1
- package/dist/{IconOffline-BwI0SYKI.js → IconOffline-Bf1mw_1N.js} +1 -1
- package/dist/{IconOneWay-Cp2QZ-lJ.js → IconOneWay-6oGoLo57.js} +1 -1
- package/dist/{IconPaid-DgXr6F7W.js → IconPaid-B3dvioAR.js} +1 -1
- package/dist/{IconWithPassport-C459CztM.js → IconPassport-5SwUf6_R.js} +4 -4
- package/dist/{IconPayout-DGzBJcuJ.js → IconPayout-b3TcXwjA.js} +1 -1
- package/dist/{IconReceipt-Dc7e8pcN.js → IconReceipt-Dh454941.js} +1 -1
- package/dist/{IconRecurrence-Czm3YpvW.js → IconRecurrence-CXVkBJ3i.js} +1 -1
- package/dist/{IconRefund-DllPRUY_.js → IconRefund-D-FNjukU.js} +1 -1
- package/dist/{IconRoundTrip-2CZyPNqg.js → IconRoundTrip-BqVPrNwg.js} +1 -1
- package/dist/{IconRouteNoStop-B-2IDDyI.js → IconRouteNoStop-CZ_QeOIY.js} +1 -1
- package/dist/{IconRouteOneStop-fdwQBGKM.js → IconRouteOneStop-DGpLAQmQ.js} +1 -1
- package/dist/{IconScheduleChange-BmJxqPtk.js → IconScheduleChange-CEIGEhU4.js} +1 -1
- package/dist/{IconSeatEmpty-BFkq1dVx.js → IconSeatEmpty-BwyVwYQZ.js} +1 -1
- package/dist/{IconSeatSold-CfEnilSn.js → IconSeatSold-B_SNoTs-.js} +1 -1
- package/dist/{IconSeatTotal-Dr1gIhZ4.js → IconSeatTotal-DUEF7k6I.js} +1 -1
- package/dist/{IconTemplate--1IoIlaU.js → IconTemplate-D1ACYaHI.js} +1 -1
- package/dist/{IconTicket-B09w5uee.js → IconTicket-5Z4b83BP.js} +1 -1
- package/dist/{IconTimer-BIpzrmmg.js → IconTimer-DbcddAPo.js} +1 -1
- package/dist/{IconTrafficControl-B_X7ClFd.js → IconTrafficControl-CEzhRpZt.js} +1 -1
- package/dist/{index-BHKeoZcE.js → index-DbgX3-2I.js} +10403 -4751
- package/dist/pimp.es.js +1 -1
- package/dist/pimp.umd.js +691 -3
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/atomic-elements/CpBadge.vue +10 -1
- package/src/components/icons/IconPassport.vue +14 -10
- package/src/components/index.ts +5 -0
- package/src/components/lists-and-table/CpTable.vue +6 -1
- package/src/components/selects/CpMultiselect.vue +426 -0
- package/src/constants/src/CpCustomIcons.ts +0 -2
- package/src/stories/CpBadge.stories.ts +18 -0
- package/src/stories/CpMultiselect.stories.ts +237 -0
- package/dist/IconPassport-tXkO0SCe.js +0 -22
- package/dist/IconSemiMoon-BkqZsW5b.js +0 -19
- package/src/components/icons/IconSemiMoon.vue +0 -29
- package/src/components/icons/IconWithPassport.vue +0 -25
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpMultiselect">
|
|
3
|
+
<base-input-label v-if="label" :required="required" class="cpMultiselect__label">
|
|
4
|
+
{{ label }}
|
|
5
|
+
</base-input-label>
|
|
6
|
+
|
|
7
|
+
<AutoComplete
|
|
8
|
+
ref="multiselect"
|
|
9
|
+
v-model="selectModel"
|
|
10
|
+
:suggestions="options"
|
|
11
|
+
:option-label="optionLabel"
|
|
12
|
+
:name="name"
|
|
13
|
+
force-selection
|
|
14
|
+
:data-key="trackBy"
|
|
15
|
+
:multiple="multiple"
|
|
16
|
+
input-class="cpMultiselect__input"
|
|
17
|
+
:invalid="isInvalid"
|
|
18
|
+
auto-option-focus
|
|
19
|
+
:placeholder="placeholder"
|
|
20
|
+
:disabled="disabled"
|
|
21
|
+
option-disabled="disabled"
|
|
22
|
+
:pt="passThroughConfig"
|
|
23
|
+
@update:model-value="handleUpdateModelValue"
|
|
24
|
+
@complete="handleSearch"
|
|
25
|
+
@keydown.esc.stop
|
|
26
|
+
>
|
|
27
|
+
<template #empty>
|
|
28
|
+
<slot name="empty">
|
|
29
|
+
<div class="cpMultiselect__empty">{{ emptyMessage }}</div>
|
|
30
|
+
</slot>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template #chip="{ value, removeCallback }">
|
|
34
|
+
<slot name="selected-option" :option="value" :remove="removeCallback">
|
|
35
|
+
<cp-badge is-clearable size="sm" @on-clear="removeCallback()">
|
|
36
|
+
<template #leading-icon>
|
|
37
|
+
<slot name="selected-option-leading-icon" :option="value" />
|
|
38
|
+
</template>
|
|
39
|
+
{{ value.name }}
|
|
40
|
+
</cp-badge>
|
|
41
|
+
</slot>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<template #option="{ option }">
|
|
45
|
+
<slot name="option" :option="option" />
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<template #dropdown>
|
|
49
|
+
<div v-if="displayPrefix" class="cpMultiselect__prefix">
|
|
50
|
+
<slot name="prefix" />
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<cp-loader v-if="isLoading" class="cpMultiselect__loader" color="#B2B2BD" />
|
|
54
|
+
<button v-else :disabled="disabled" type="button" class="cpMultiselect__toggle" @click.stop="toggleDropdown">
|
|
55
|
+
<cp-icon type="chevron-down" class="cpMultiselect__dropdownIcon" :class="chevronDynamicClass" />
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
<base-select-clear-button v-if="displayClearButton" class="cpMultiselect__clear" @click="handleClear" />
|
|
59
|
+
</template>
|
|
60
|
+
</AutoComplete>
|
|
61
|
+
|
|
62
|
+
<transition-expand>
|
|
63
|
+
<p v-if="isInvalid" class="cpMultiselect__error">
|
|
64
|
+
{{ errorMessage }}
|
|
65
|
+
</p>
|
|
66
|
+
</transition-expand>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script setup>
|
|
71
|
+
import { ref, computed, onMounted } from 'vue'
|
|
72
|
+
import AutoComplete from 'primevue/autocomplete'
|
|
73
|
+
|
|
74
|
+
import { absolutePosition, getOuterWidth } from '@primeuix/utils/dom'
|
|
75
|
+
|
|
76
|
+
import BaseInputLabel from '@/components/core/BaseInputLabel.vue'
|
|
77
|
+
import TransitionExpand from '@/components/helpers-utilities/TransitionExpand.vue'
|
|
78
|
+
import BaseSelectClearButton from '@/components/core/BaseSelectClearButton.vue'
|
|
79
|
+
|
|
80
|
+
import { isEmpty } from '@/helpers/object'
|
|
81
|
+
|
|
82
|
+
const props = defineProps({
|
|
83
|
+
label: {
|
|
84
|
+
type: String,
|
|
85
|
+
required: false,
|
|
86
|
+
default: '',
|
|
87
|
+
},
|
|
88
|
+
required: {
|
|
89
|
+
type: Boolean,
|
|
90
|
+
required: false,
|
|
91
|
+
default: false,
|
|
92
|
+
},
|
|
93
|
+
name: {
|
|
94
|
+
type: String,
|
|
95
|
+
required: false,
|
|
96
|
+
default: '',
|
|
97
|
+
},
|
|
98
|
+
placeholder: {
|
|
99
|
+
type: String,
|
|
100
|
+
required: false,
|
|
101
|
+
default: '',
|
|
102
|
+
},
|
|
103
|
+
isInvalid: {
|
|
104
|
+
type: Boolean,
|
|
105
|
+
required: false,
|
|
106
|
+
default: false,
|
|
107
|
+
},
|
|
108
|
+
isClearable: {
|
|
109
|
+
type: Boolean,
|
|
110
|
+
required: false,
|
|
111
|
+
default: false,
|
|
112
|
+
},
|
|
113
|
+
isLoading: {
|
|
114
|
+
type: Boolean,
|
|
115
|
+
required: false,
|
|
116
|
+
default: false,
|
|
117
|
+
},
|
|
118
|
+
disabled: {
|
|
119
|
+
type: Boolean,
|
|
120
|
+
required: false,
|
|
121
|
+
default: false,
|
|
122
|
+
},
|
|
123
|
+
multiple: {
|
|
124
|
+
type: Boolean,
|
|
125
|
+
required: false,
|
|
126
|
+
default: false,
|
|
127
|
+
},
|
|
128
|
+
options: {
|
|
129
|
+
type: Array,
|
|
130
|
+
required: false,
|
|
131
|
+
default: () => [],
|
|
132
|
+
},
|
|
133
|
+
optionLabel: {
|
|
134
|
+
type: String,
|
|
135
|
+
required: false,
|
|
136
|
+
default: 'name',
|
|
137
|
+
},
|
|
138
|
+
trackBy: {
|
|
139
|
+
type: String,
|
|
140
|
+
required: false,
|
|
141
|
+
default: 'id',
|
|
142
|
+
},
|
|
143
|
+
emptyMessage: {
|
|
144
|
+
type: String,
|
|
145
|
+
required: false,
|
|
146
|
+
default: 'No results found',
|
|
147
|
+
},
|
|
148
|
+
errorMessage: {
|
|
149
|
+
type: String,
|
|
150
|
+
required: false,
|
|
151
|
+
default: '',
|
|
152
|
+
},
|
|
153
|
+
modelValue: {
|
|
154
|
+
type: [Array, Object],
|
|
155
|
+
required: false,
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const emit = defineEmits(['search', 'select', 'clear'])
|
|
160
|
+
|
|
161
|
+
const selectModel = computed({
|
|
162
|
+
get() {
|
|
163
|
+
return props.modelValue
|
|
164
|
+
},
|
|
165
|
+
set(value) {
|
|
166
|
+
if (typeof value === 'string') {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
emit('update:modelValue', value)
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const passThroughConfig = {
|
|
175
|
+
root: { class: 'cpMultiselect__select' },
|
|
176
|
+
inputmultiple: { class: 'cpMultiselect__tags' },
|
|
177
|
+
dropdown: { class: 'cpMultiselect__toggle' },
|
|
178
|
+
inputchip: { class: 'cpMultiselect__inputWrapper' },
|
|
179
|
+
overlay: { class: 'cpMultiselect__overlay' },
|
|
180
|
+
listcontainer: { class: 'cpMultiselect__listWrapper' },
|
|
181
|
+
list: { class: 'cpMultiselect__list' },
|
|
182
|
+
option: { class: 'cpMultiselect__option' },
|
|
183
|
+
loader: { class: 'cpMultiselect__hidden' },
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const multiselect = ref(null)
|
|
187
|
+
|
|
188
|
+
const isDropdownOpen = computed(() => multiselect.value?.overlayVisible)
|
|
189
|
+
|
|
190
|
+
const chevronDynamicClass = computed(() => {
|
|
191
|
+
return {
|
|
192
|
+
'cpMultiselect__dropdownIcon--isRotated': isDropdownOpen.value,
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const displayPrefix = computed(() => {
|
|
197
|
+
if (!props.multiple) return true
|
|
198
|
+
return !selectModel.value?.length
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const displayClearButton = computed(() => {
|
|
202
|
+
if (props.multiple) return false
|
|
203
|
+
return props.isClearable && !isEmpty(selectModel.value)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const handleSearch = (event) => emit('search', event.query)
|
|
207
|
+
const handleClear = () => (selectModel.value = null)
|
|
208
|
+
|
|
209
|
+
const toggleDropdown = () => {
|
|
210
|
+
if (isDropdownOpen.value) {
|
|
211
|
+
multiselect.value.hide()
|
|
212
|
+
} else {
|
|
213
|
+
multiselect.value.show()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const handleUpdateModelValue = (value) => {
|
|
218
|
+
// Autocomplete will set the model value to the query string if not blocked
|
|
219
|
+
if (!value || typeof value === 'string') {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const overrideAlignOverlay = () => (multiselect.value.alignOverlay = alignOverlay)
|
|
225
|
+
|
|
226
|
+
const alignOverlay = () => {
|
|
227
|
+
const target = multiselect.value.$el
|
|
228
|
+
if (!multiselect.value.overlay || !target) return
|
|
229
|
+
|
|
230
|
+
multiselect.value.overlay.style.width = `${getOuterWidth(target)}px`
|
|
231
|
+
|
|
232
|
+
absolutePosition(multiselect.value.overlay, target)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
onMounted(() => overrideAlignOverlay())
|
|
236
|
+
</script>
|
|
237
|
+
|
|
238
|
+
<style lang="scss">
|
|
239
|
+
.cpMultiselect {
|
|
240
|
+
display: flex;
|
|
241
|
+
flex-direction: column;
|
|
242
|
+
gap: sp.$space;
|
|
243
|
+
|
|
244
|
+
&__label {
|
|
245
|
+
margin-bottom: 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
&__prefix {
|
|
249
|
+
order: -1;
|
|
250
|
+
|
|
251
|
+
&:empty {
|
|
252
|
+
display: none;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
&__select {
|
|
257
|
+
display: flex;
|
|
258
|
+
min-height: fn.px-to-rem(46);
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: space-between;
|
|
261
|
+
padding: fn.px-to-rem(8);
|
|
262
|
+
border: 1px solid colors.$border-color;
|
|
263
|
+
border-radius: fn.px-to-rem(10);
|
|
264
|
+
gap: sp.$space;
|
|
265
|
+
|
|
266
|
+
&:has(input:focus-visible) {
|
|
267
|
+
box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$primary-color, $lightness: 70%);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
&:has(input:disabled) {
|
|
271
|
+
background-color: colors.$neutral-light-1;
|
|
272
|
+
cursor: not-allowed;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
&:has([data-p='invalid']) {
|
|
276
|
+
border-color: colors.$error-color;
|
|
277
|
+
|
|
278
|
+
&:has(input:focus-visible) {
|
|
279
|
+
box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$error-color, $lightness: 70%);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
&__tags {
|
|
285
|
+
display: flex;
|
|
286
|
+
flex: 1;
|
|
287
|
+
flex-wrap: wrap;
|
|
288
|
+
gap: sp.$space;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
&__toggle {
|
|
292
|
+
@extend %u-focus-outline;
|
|
293
|
+
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
justify-content: center;
|
|
297
|
+
|
|
298
|
+
&:disabled {
|
|
299
|
+
cursor: not-allowed;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
&__inputWrapper {
|
|
304
|
+
display: flex;
|
|
305
|
+
flex: 1;
|
|
306
|
+
align-items: center;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
&__input {
|
|
310
|
+
padding: 0;
|
|
311
|
+
flex: 1;
|
|
312
|
+
font-size: fn.px-to-rem(14);
|
|
313
|
+
line-height: fn.px-to-rem(24);
|
|
314
|
+
background-color: transparent;
|
|
315
|
+
|
|
316
|
+
&:disabled {
|
|
317
|
+
cursor: not-allowed;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
&__input {
|
|
322
|
+
padding: 0;
|
|
323
|
+
flex: 1;
|
|
324
|
+
font-size: fn.px-to-rem(14);
|
|
325
|
+
line-height: fn.px-to-rem(24);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
&__dropdownIcon {
|
|
329
|
+
@include mx.square-sizing(16);
|
|
330
|
+
|
|
331
|
+
transition: rotate 200ms ease;
|
|
332
|
+
|
|
333
|
+
&--isRotated {
|
|
334
|
+
rotate: 180deg;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
&__loader {
|
|
339
|
+
@include mx.square-sizing(16);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
&__overlay {
|
|
343
|
+
left: 0;
|
|
344
|
+
overflow: hidden;
|
|
345
|
+
background: colors.$neutral-light;
|
|
346
|
+
border-radius: fn.px-to-rem(8);
|
|
347
|
+
margin-block: sp.$space;
|
|
348
|
+
box-shadow:
|
|
349
|
+
0 2px 4px 0 rgba(18, 18, 23, 0.04),
|
|
350
|
+
0 5px 8px 0 rgba(18, 18, 23, 0.04),
|
|
351
|
+
0 10px 18px 0 rgba(18, 18, 23, 0.03),
|
|
352
|
+
0 24px 48px 0 rgba(18, 18, 23, 0.03),
|
|
353
|
+
0 0 0 1px rgba(18, 18, 23, 0.1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
&__listWrapper {
|
|
357
|
+
overflow-y: auto;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
&__hidden {
|
|
361
|
+
display: none;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
&__list {
|
|
365
|
+
padding: sp.$space-sm;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
&__option {
|
|
369
|
+
display: flex;
|
|
370
|
+
align-items: center;
|
|
371
|
+
padding: fn.px-to-rem(10);
|
|
372
|
+
border-radius: fn.px-to-rem(6);
|
|
373
|
+
gap: sp.$space-sm;
|
|
374
|
+
font-size: fn.px-to-rem(14);
|
|
375
|
+
line-height: fn.px-to-rem(18);
|
|
376
|
+
|
|
377
|
+
&:hover,
|
|
378
|
+
&[data-p-focused='true'],
|
|
379
|
+
&[data-p-selected='true'] {
|
|
380
|
+
background: colors.$neutral-dark-5;
|
|
381
|
+
color: colors.$neutral-dark;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
&[data-p-selected='true'] {
|
|
385
|
+
border: 1px dashed colors.$border-color;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
&[data-p-disabled='true'] {
|
|
389
|
+
opacity: 0.5;
|
|
390
|
+
cursor: not-allowed;
|
|
391
|
+
pointer-events: none;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
&__empty {
|
|
396
|
+
padding: sp.$space;
|
|
397
|
+
font-size: fn.px-to-rem(14);
|
|
398
|
+
color: colors.$neutral-dark-1;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
&__error {
|
|
402
|
+
color: colors.$error-color;
|
|
403
|
+
font-size: fn.px-to-rem(14);
|
|
404
|
+
font-weight: 500;
|
|
405
|
+
line-height: fn.px-to-rem(24);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@include mx.media-query-pointer-device-only {
|
|
409
|
+
&__clear {
|
|
410
|
+
display: none;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
@include mx.media-query-pointer-device-only {
|
|
416
|
+
.cpMultiselect:has(.cpMultiselect__clear):is(:hover, :focus-within) {
|
|
417
|
+
.cpMultiselect__clear {
|
|
418
|
+
display: flex;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.cpMultiselect__toggle {
|
|
422
|
+
display: none;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
</style>
|
|
@@ -63,7 +63,6 @@ export default {
|
|
|
63
63
|
'seat-empty': defineAsyncComponent(() => import('@/components/icons/IconSeatEmpty.vue')),
|
|
64
64
|
'seat-sold': defineAsyncComponent(() => import('@/components/icons/IconSeatSold.vue')),
|
|
65
65
|
'seat-total': defineAsyncComponent(() => import('@/components/icons/IconSeatTotal.vue')),
|
|
66
|
-
'semi-moon': defineAsyncComponent(() => import('@/components/icons/IconSemiMoon.vue')),
|
|
67
66
|
supplier: defineAsyncComponent(() => import('@/components/icons/IconSupplier.vue')),
|
|
68
67
|
template: defineAsyncComponent(() => import('@/components/icons/IconTemplate.vue')),
|
|
69
68
|
'third-party': defineAsyncComponent(() => import('@/components/icons/IconThirdParty.vue')),
|
|
@@ -71,5 +70,4 @@ export default {
|
|
|
71
70
|
timer: defineAsyncComponent(() => import('@/components/icons/IconTimer.vue')),
|
|
72
71
|
tooltip: defineAsyncComponent(() => import('@/components/icons/IconTooltip.vue')),
|
|
73
72
|
'traffic-control': defineAsyncComponent(() => import('@/components/icons/IconTrafficControl.vue')),
|
|
74
|
-
'with-passport': defineAsyncComponent(() => import('@/components/icons/IconWithPassport.vue')),
|
|
75
73
|
}
|
|
@@ -20,6 +20,10 @@ const meta = {
|
|
|
20
20
|
control: 'boolean',
|
|
21
21
|
description: 'Whether the badge has a stroked border',
|
|
22
22
|
},
|
|
23
|
+
isSquare: {
|
|
24
|
+
control: 'boolean',
|
|
25
|
+
description: 'Whether the badge has a square border',
|
|
26
|
+
},
|
|
23
27
|
leadingIcon: {
|
|
24
28
|
control: 'select',
|
|
25
29
|
options: ['check', 'arrow-right', 'arrow-left', 'arrow-up', 'arrow-down'],
|
|
@@ -46,6 +50,7 @@ export const Default: Story = {
|
|
|
46
50
|
color: 'gray',
|
|
47
51
|
size: 'md',
|
|
48
52
|
isStroked: false,
|
|
53
|
+
isSquare: false,
|
|
49
54
|
leadingIcon: '',
|
|
50
55
|
trailingIcon: '',
|
|
51
56
|
label: 'Badge',
|
|
@@ -115,6 +120,19 @@ export const WithIcons: Story = {
|
|
|
115
120
|
}),
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
export const IsSquare: Story = {
|
|
124
|
+
render: () => ({
|
|
125
|
+
components: { CpBadge },
|
|
126
|
+
template: `
|
|
127
|
+
<div style="display: flex; gap: 8px; align-items: center;">
|
|
128
|
+
<CpBadge color="blue" isSquare label="Square" />
|
|
129
|
+
<CpBadge color="green" isSquare label="Square" />
|
|
130
|
+
<CpBadge color="red" isSquare label="Square" />
|
|
131
|
+
</div>
|
|
132
|
+
`,
|
|
133
|
+
}),
|
|
134
|
+
}
|
|
135
|
+
|
|
118
136
|
export const IsClearable: Story = {
|
|
119
137
|
render: () => ({
|
|
120
138
|
components: { CpBadge },
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import CpMultiselect from '@/components/selects/CpMultiselect.vue'
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'CpMultiSelect',
|
|
8
|
+
component: CpMultiselect,
|
|
9
|
+
argTypes: {
|
|
10
|
+
label: {
|
|
11
|
+
control: 'text',
|
|
12
|
+
description: 'Label of the select',
|
|
13
|
+
},
|
|
14
|
+
required: {
|
|
15
|
+
control: 'boolean',
|
|
16
|
+
description: 'Whether the select is required',
|
|
17
|
+
},
|
|
18
|
+
name: {
|
|
19
|
+
control: 'text',
|
|
20
|
+
description: 'Name of the select',
|
|
21
|
+
},
|
|
22
|
+
placeholder: {
|
|
23
|
+
control: 'text',
|
|
24
|
+
description: 'Placeholder of the select',
|
|
25
|
+
},
|
|
26
|
+
isInvalid: {
|
|
27
|
+
control: 'boolean',
|
|
28
|
+
description: 'Whether the select is invalid',
|
|
29
|
+
},
|
|
30
|
+
multiple: {
|
|
31
|
+
control: 'boolean',
|
|
32
|
+
description: 'Whether the select should be multiple',
|
|
33
|
+
},
|
|
34
|
+
options: {
|
|
35
|
+
control: 'object',
|
|
36
|
+
description: 'Options of the select',
|
|
37
|
+
},
|
|
38
|
+
errorMessage: {
|
|
39
|
+
control: 'text',
|
|
40
|
+
description: 'Error message of the select',
|
|
41
|
+
},
|
|
42
|
+
disabled: {
|
|
43
|
+
control: 'boolean',
|
|
44
|
+
description: 'Whether the select is disabled',
|
|
45
|
+
},
|
|
46
|
+
isLoading: {
|
|
47
|
+
control: 'boolean',
|
|
48
|
+
description: 'Whether the select is loading',
|
|
49
|
+
},
|
|
50
|
+
emptyMessage: {
|
|
51
|
+
control: 'text',
|
|
52
|
+
description: 'Message displayed when no options are found',
|
|
53
|
+
},
|
|
54
|
+
trackBy: {
|
|
55
|
+
control: 'text',
|
|
56
|
+
description: 'Property to track the selected option',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
} satisfies Meta<typeof CpMultiselect>
|
|
60
|
+
|
|
61
|
+
export default meta
|
|
62
|
+
type Story = StoryObj<typeof meta>
|
|
63
|
+
|
|
64
|
+
export const Single: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
placeholder: 'Select a supplier',
|
|
67
|
+
multiple: false,
|
|
68
|
+
options: [
|
|
69
|
+
{ id: 1, name: 'MGA AIRLINES' },
|
|
70
|
+
{ id: 2, name: 'ZENITH - EUROATLANTIC AIRWAYS' },
|
|
71
|
+
{ id: 3, name: 'ZENITH - SOUTHERN AIRWAYS EXPRESS MOKULELE AIRLINES AND SURF AIR MOBILITY' },
|
|
72
|
+
{ id: 4, name: 'EDO TUNISAIR ITALY' },
|
|
73
|
+
{ id: 5, name: 'LMX SUISSE SA' },
|
|
74
|
+
{ id: 6, name: 'LMX VOYAGES SAS' },
|
|
75
|
+
{ id: 7, name: 'FLY KHIVA TRAVEL' },
|
|
76
|
+
{ id: 8, name: 'ZENITH - AIR CHATHAMS' },
|
|
77
|
+
{ id: 9, name: 'CP EMPLOYEES' },
|
|
78
|
+
{ id: 10, name: 'MONDIAL TOURISME' },
|
|
79
|
+
{ id: 11, name: 'UNITRAVEL UTAZÁSI IRODA' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
render: (args) => ({
|
|
83
|
+
components: { CpMultiselect },
|
|
84
|
+
setup() {
|
|
85
|
+
const searchQuery = ref('')
|
|
86
|
+
const isLoading = ref(false)
|
|
87
|
+
|
|
88
|
+
const originalOptions = ref(args.options)
|
|
89
|
+
const dynamicOptions = ref(originalOptions.value)
|
|
90
|
+
|
|
91
|
+
const handleSearch = async (query: string) => {
|
|
92
|
+
isLoading.value = true
|
|
93
|
+
searchQuery.value = query
|
|
94
|
+
|
|
95
|
+
dynamicOptions.value = originalOptions.value
|
|
96
|
+
|
|
97
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
98
|
+
|
|
99
|
+
dynamicOptions.value = dynamicOptions.value.filter((option: { name: string }) => {
|
|
100
|
+
return option.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
isLoading.value = false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const selectedSupplier = ref(null)
|
|
107
|
+
return { args, selectedSupplier, dynamicOptions, handleSearch, isLoading }
|
|
108
|
+
},
|
|
109
|
+
template: `
|
|
110
|
+
<div style="padding: 20px;">
|
|
111
|
+
<CpMultiselect v-model="selectedSupplier" v-bind="args" :options="dynamicOptions" :is-loading="isLoading" @search="handleSearch">
|
|
112
|
+
<template #prefix>
|
|
113
|
+
<cp-partner-badge type="supplier" size="xs" />
|
|
114
|
+
</template>
|
|
115
|
+
<template #option="{ option }">
|
|
116
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
117
|
+
<cp-partner-badge type="supplier" size="xs" />
|
|
118
|
+
{{ option.name }}
|
|
119
|
+
</div>
|
|
120
|
+
</template>
|
|
121
|
+
</CpMultiselect>
|
|
122
|
+
</div>
|
|
123
|
+
`,
|
|
124
|
+
}),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const Multiple: Story = {
|
|
128
|
+
args: {
|
|
129
|
+
required: false,
|
|
130
|
+
name: 'select',
|
|
131
|
+
placeholder: 'All airlines',
|
|
132
|
+
isInvalid: false,
|
|
133
|
+
disabled: false,
|
|
134
|
+
multiple: true,
|
|
135
|
+
emptyMessage: 'No airlines found',
|
|
136
|
+
options: [
|
|
137
|
+
{ id: 1, name: 'United Airlines', iata_code: 'UA' },
|
|
138
|
+
{ id: 2, name: 'Delta Airlines', iata_code: 'DL' },
|
|
139
|
+
{ id: 3, name: 'American Airlines', iata_code: 'AA' },
|
|
140
|
+
{ id: 4, name: 'Southwest Airlines', iata_code: 'WN' },
|
|
141
|
+
{ id: 5, name: 'Alaska Airlines', iata_code: 'AS' },
|
|
142
|
+
{ id: 6, name: 'JetBlue Airways', iata_code: 'B6' },
|
|
143
|
+
{ id: 7, name: 'Spirit Airlines', iata_code: 'NK' },
|
|
144
|
+
{ id: 8, name: 'Frontier Airlines', iata_code: 'F9' },
|
|
145
|
+
{ id: 9, name: 'Hawaiian Airlines', iata_code: 'HA' },
|
|
146
|
+
{ id: 10, name: 'SkyWest Airlines', iata_code: 'OO' },
|
|
147
|
+
{ id: 11, name: 'Allegiant Air', iata_code: 'G4' },
|
|
148
|
+
{ id: 12, name: 'Atlantic Southeast Airlines', iata_code: 'EV' },
|
|
149
|
+
{ id: 13, name: 'American Eagle Airlines', iata_code: 'MQ' },
|
|
150
|
+
{ id: 14, name: 'Alaska Airlines', iata_code: 'AS' },
|
|
151
|
+
{ id: 15, name: 'Air France', iata_code: 'AF', disabled: true },
|
|
152
|
+
{ id: 16, name: 'Air Canada', iata_code: 'AC' },
|
|
153
|
+
{ id: 17, name: 'Air New Zealand', iata_code: 'NZ' },
|
|
154
|
+
{ id: 18, name: 'Air China', iata_code: 'CA' },
|
|
155
|
+
{ id: 19, name: 'Air India', iata_code: 'AI' },
|
|
156
|
+
{ id: 20, name: 'Air Berlin', iata_code: 'AB' },
|
|
157
|
+
{ id: 21, name: 'AirAsia', iata_code: 'AK' },
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
render: (args) => ({
|
|
161
|
+
components: { CpMultiselect },
|
|
162
|
+
setup() {
|
|
163
|
+
const searchQuery = ref('')
|
|
164
|
+
const isLoading = ref(false)
|
|
165
|
+
|
|
166
|
+
const originalOptions = ref(args.options)
|
|
167
|
+
const dynamicOptions = ref(originalOptions.value)
|
|
168
|
+
|
|
169
|
+
const handleSearch = async (query: string) => {
|
|
170
|
+
isLoading.value = true
|
|
171
|
+
searchQuery.value = query
|
|
172
|
+
|
|
173
|
+
if (!searchQuery.value) return (dynamicOptions.value = originalOptions.value)
|
|
174
|
+
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
176
|
+
|
|
177
|
+
dynamicOptions.value = dynamicOptions.value.filter((option: { name: string }) => {
|
|
178
|
+
return option.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
isLoading.value = false
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const selectedAirlines = ref([])
|
|
185
|
+
return { args, selectedAirlines, dynamicOptions, handleSearch, isLoading }
|
|
186
|
+
},
|
|
187
|
+
template: `
|
|
188
|
+
<div style="padding: 20px;">
|
|
189
|
+
<CpMultiselect v-model="selectedAirlines" v-bind="args" :options="dynamicOptions" :is-loading="isLoading" @search="handleSearch">
|
|
190
|
+
<template #prefix>
|
|
191
|
+
<cp-partner-badge type="airline" size="xs" />
|
|
192
|
+
</template>
|
|
193
|
+
<template #selected-option-leading-icon="{ option }">
|
|
194
|
+
<cp-airline-logo :iata-code="option.iata_code" size="14" />
|
|
195
|
+
</template>
|
|
196
|
+
<template #option="{ option }">
|
|
197
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
198
|
+
<cp-airline-logo :iata-code="option.iata_code" size="14" />
|
|
199
|
+
{{ option.name }}
|
|
200
|
+
</div>
|
|
201
|
+
</template>
|
|
202
|
+
</CpMultiselect>
|
|
203
|
+
</div>
|
|
204
|
+
`,
|
|
205
|
+
}),
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const Invalid: Story = {
|
|
209
|
+
args: {
|
|
210
|
+
placeholder: 'Select a supplier',
|
|
211
|
+
disabled: false,
|
|
212
|
+
isInvalid: true,
|
|
213
|
+
options: [],
|
|
214
|
+
},
|
|
215
|
+
render: (args) => ({
|
|
216
|
+
components: { CpMultiselect },
|
|
217
|
+
setup() {
|
|
218
|
+
const selectedSupplier = ref(null)
|
|
219
|
+
return { args, selectedSupplier }
|
|
220
|
+
},
|
|
221
|
+
template: `
|
|
222
|
+
<div style="padding: 20px;">
|
|
223
|
+
<CpMultiselect v-model="selectedSupplier" v-bind="args">
|
|
224
|
+
<template #prefix>
|
|
225
|
+
<cp-partner-badge type="supplier" size="xs" />
|
|
226
|
+
</template>
|
|
227
|
+
<template #option="{ option }">
|
|
228
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
229
|
+
<cp-partner-badge type="supplier" size="xs" />
|
|
230
|
+
{{ option.name }}
|
|
231
|
+
</div>
|
|
232
|
+
</template>
|
|
233
|
+
</CpMultiselect>
|
|
234
|
+
</div>
|
|
235
|
+
`,
|
|
236
|
+
}),
|
|
237
|
+
}
|