@citizenplane/pimp 8.32.2 → 8.32.5

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.
@@ -30,7 +30,7 @@
30
30
  </template>
31
31
  <template #chip="{ value, removeCallback }">
32
32
  <slot name="selected-option" :option="value" :remove="removeCallback">
33
- <cp-badge is-clearable size="sm" @on-clear="removeCallback()">
33
+ <cp-badge is-clearable size="sm" @on-clear="removeCallback">
34
34
  <template #leading-icon>
35
35
  <slot name="selected-option-leading-icon" :option="value" />
36
36
  </template>
@@ -73,7 +73,6 @@ import { isEmpty } from '@/helpers/object'
73
73
 
74
74
  interface Emits {
75
75
  (e: 'search', query: string): void
76
- (e: 'select', option: Record<string, unknown>): void
77
76
  (e: 'clear'): void
78
77
  (e: 'update:modelValue', value: Record<string, unknown> | Record<string, unknown>[] | null): void
79
78
  }
@@ -129,8 +128,14 @@ const selectModel = computed({
129
128
  },
130
129
  })
131
130
 
132
- const passThroughConfig = {
133
- root: { class: 'cpMultiselect__select' },
131
+ const hasMultipleValues = computed(() => Array.isArray(selectModel.value) && selectModel.value.length > 0)
132
+
133
+ const multiselectDynamicClass = computed(() => {
134
+ return hasMultipleValues.value ? 'cpMultiselect__select--hasMultipleValues' : ''
135
+ })
136
+
137
+ const passThroughConfig = computed(() => ({
138
+ root: { class: `cpMultiselect__select ${multiselectDynamicClass.value}` },
134
139
  inputmultiple: { class: 'cpMultiselect__tags' },
135
140
  dropdown: { class: 'cpMultiselect__toggle' },
136
141
  inputchip: { class: 'cpMultiselect__inputWrapper' },
@@ -139,16 +144,15 @@ const passThroughConfig = {
139
144
  list: { class: 'cpMultiselect__list' },
140
145
  option: { class: 'cpMultiselect__option' },
141
146
  loader: { class: 'cpMultiselect__hidden' },
142
- }
147
+ }))
143
148
 
144
149
  const multiselect = ref<InstanceType<typeof AutoComplete> | null>(null)
145
150
 
151
+ // @ts-expect-error 'overlayVisible' does not exist on type instance of AutoComplete
146
152
  const isDropdownOpen = computed(() => multiselect.value?.overlayVisible)
147
153
 
148
154
  const chevronDynamicClass = computed(() => {
149
- return {
150
- 'cpMultiselect__dropdownIcon--isRotated': isDropdownOpen.value,
151
- }
155
+ return { 'cpMultiselect__dropdownIcon--isRotated': isDropdownOpen.value }
152
156
  })
153
157
 
154
158
  const displayPrefix = computed(() => {
@@ -166,8 +170,10 @@ const handleClear = () => (selectModel.value = null)
166
170
 
167
171
  const toggleDropdown = () => {
168
172
  if (isDropdownOpen.value) {
173
+ // @ts-expect-error 'hide' does not exist on type instance of AutoComplete
169
174
  multiselect.value?.hide()
170
175
  } else {
176
+ // @ts-expect-error 'show' does not exist on type instance of AutoComplete
171
177
  multiselect.value?.show()
172
178
  }
173
179
  }
@@ -181,16 +187,22 @@ const handleUpdateModelValue = (value: Record<string, unknown> | null) => {
181
187
 
182
188
  const overrideAlignOverlay = () => {
183
189
  if (multiselect.value) {
190
+ // @ts-expect-error 'alignOverlay' does not exist on type instance of AutoComplete
184
191
  multiselect.value.alignOverlay = alignOverlay
185
192
  }
186
193
  }
187
194
 
188
195
  const alignOverlay = () => {
196
+ // @ts-expect-error 'el' does not exist on type instance of AutoComplete
189
197
  const target = multiselect.value?.$el
198
+
199
+ // @ts-expect-error 'overlay' does not exist on type instance of AutoComplete
190
200
  if (!multiselect.value?.overlay || !target) return
191
201
 
202
+ // @ts-expect-error 'overlay' does not exist on type instance of AutoComplete
192
203
  multiselect.value.overlay.style.width = `${getOuterWidth(target)}px`
193
204
 
205
+ // @ts-expect-error 'overlay' does not exist on type instance of AutoComplete
194
206
  absolutePosition(multiselect.value.overlay, target)
195
207
  }
196
208
 
@@ -208,6 +220,9 @@ onMounted(() => overrideAlignOverlay())
208
220
  }
209
221
 
210
222
  &__prefix {
223
+ display: flex;
224
+ align-items: center;
225
+ flex-shrink: 0;
211
226
  order: -1;
212
227
 
213
228
  &:empty {
@@ -217,14 +232,17 @@ onMounted(() => overrideAlignOverlay())
217
232
 
218
233
  &__select {
219
234
  display: flex;
220
- min-height: fn.px-to-rem(46);
221
235
  align-items: center;
222
236
  justify-content: space-between;
223
- padding: fn.px-to-rem(8);
237
+ padding: fn.px-to-rem(10.5);
224
238
  border: 1px solid colors.$border-color;
225
239
  border-radius: fn.px-to-rem(10);
226
240
  gap: sp.$space;
227
241
 
242
+ &--hasMultipleValues {
243
+ padding: fn.px-to-rem(9.5) fn.px-to-rem(10.5);
244
+ }
245
+
228
246
  &:has(input:focus-visible) {
229
247
  box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$primary-color, $lightness: 70%);
230
248
  }
@@ -272,21 +290,15 @@ onMounted(() => overrideAlignOverlay())
272
290
  padding: 0;
273
291
  flex: 1;
274
292
  font-size: fn.px-to-rem(14);
275
- line-height: fn.px-to-rem(24);
293
+ line-height: fn.px-to-rem(22);
276
294
  background-color: transparent;
295
+ width: 100%;
277
296
 
278
297
  &:disabled {
279
298
  cursor: not-allowed;
280
299
  }
281
300
  }
282
301
 
283
- &__input {
284
- padding: 0;
285
- flex: 1;
286
- font-size: fn.px-to-rem(14);
287
- line-height: fn.px-to-rem(24);
288
- }
289
-
290
302
  &__dropdownIcon {
291
303
  @include mx.square-sizing(16);
292
304
 
@@ -1,45 +1,33 @@
1
1
  import { vTooltip } from 'floating-vue'
2
- // PLUGINS
3
2
  import { vMaska } from 'maska/vue'
4
3
  import PrimeVue from 'primevue/config'
5
4
  import { App } from 'vue'
6
5
  import { BindOnceDirective } from 'vue-bind-once'
7
6
 
8
- // DIRECTIVES
9
7
  import ClickOutside from '../directives/ClickOutside'
10
8
  import CpCoreDatepicker from '../libs/CoreDatepicker.vue'
11
9
  import CpAirlineLogo from './CpAirlineLogo.vue'
12
- // Feedback indicators
13
10
  import CpAlert from './CpAlert.vue'
14
- // COMPONENTS
15
- // Atomic elements
16
11
  import CpBadge from './CpBadge.vue'
17
- // Buttons
18
12
  import CpButton from './CpButton.vue'
19
13
  import CpCalendar from './CpCalendar.vue'
20
- // Toggles
21
14
  import CpCheckbox from './CpCheckbox.vue'
22
- // Date pickers
15
+ import CpContextualMenu from './CpContextualMenu.vue'
23
16
  import CpDate from './CpDate.vue'
24
17
  import CpDatepicker from './CpDatepicker.vue'
25
18
  import CpDialog from './CpDialog.vue'
26
19
  import CpDialogWrapper from './CpDialogWrapper.vue'
27
- // Typography
28
20
  import CpHeading from './CpHeading.vue'
29
- // Visual
30
21
  import CpIcon from './CpIcon.vue'
31
- // Inputs
32
22
  import CpInput from './CpInput.vue'
33
- // Dropdown menus
34
23
  import CpLoader from './CpLoader.vue'
24
+ import CpMenuItem from './CpMenuItem.vue'
35
25
  import CpMultiselect from './CpMultiselect.vue'
36
26
  import CpPartnerBadge from './CpPartnerBadge.vue'
37
27
  import CpRadio from './CpRadio.vue'
38
- // Selects
39
28
  import CpSelect from './CpSelect.vue'
40
29
  import CpSelectMenu from './CpSelectMenu.vue'
41
30
  import CpSwitch from './CpSwitch.vue'
42
- // List and Tables
43
31
  import CpTable from './CpTable.vue'
44
32
  import CpTextarea from './CpTextarea.vue'
45
33
  import CpToaster from './CpToaster.vue'
@@ -52,11 +40,8 @@ import IconGroupBy from './icons/IconGroupBy.vue'
52
40
  import IconOta from './icons/IconOta.vue'
53
41
  import IconSupplier from './icons/IconSupplier.vue'
54
42
  import IconThirdParty from './icons/IconThirdParty.vue'
55
- // Icons
56
43
  import IconTooltip from './icons/IconTooltip.vue'
57
- // Helpers and Utilities
58
44
  import TransitionExpand from './TransitionExpand.vue'
59
- // Methods
60
45
  import createToaster from '@/plugins/toaster'
61
46
 
62
47
  const Components = {
@@ -67,6 +52,8 @@ const Components = {
67
52
  CpDialogWrapper,
68
53
  CpDialog,
69
54
  CpDate,
55
+ CpContextualMenu,
56
+ CpMenuItem,
70
57
  CpCoreDatepicker,
71
58
  CpDatepicker,
72
59
  CpCalendar,
@@ -0,0 +1,68 @@
1
+ import { computed, ref } from 'vue'
2
+
3
+ import type { Meta, StoryObj } from '@storybook/vue3'
4
+
5
+ import CpContextualMenu from '@/components/CpContextualMenu.vue'
6
+
7
+ const meta = {
8
+ title: 'CpContextualMenu',
9
+ component: CpContextualMenu,
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component:
14
+ 'A component that displays airline logos using IATA codes. Fetches logos from Kiwi.com CDN and displays them with customizable sizes.',
15
+ },
16
+ },
17
+ },
18
+ argTypes: {
19
+ items: {
20
+ control: 'object',
21
+ description: 'The items to display in the menu',
22
+ table: {
23
+ type: { summary: 'object' },
24
+ defaultValue: { summary: '[]' },
25
+ },
26
+ },
27
+ },
28
+ tags: ['autodocs'],
29
+ } satisfies Meta<typeof CpContextualMenu>
30
+
31
+ export default meta
32
+ type Story = StoryObj<typeof meta>
33
+
34
+ export const Default: Story = {
35
+ render: (args) => ({
36
+ components: { CpContextualMenu },
37
+ setup() {
38
+ const menu = ref<InstanceType<typeof CpContextualMenu>>()
39
+ const showMenu = (event: MouseEvent) => menu.value?.show(event)
40
+
41
+ const isLoading = ref(false)
42
+
43
+ const items = computed(() => [
44
+ {
45
+ label: 'Download',
46
+ icon: 'download',
47
+ isLoading: isLoading.value,
48
+ command: () => {
49
+ isLoading.value = true
50
+ setTimeout(() => (isLoading.value = false), 2000)
51
+ },
52
+ },
53
+ {
54
+ label: 'Delete',
55
+ icon: 'trash-2',
56
+ isCritical: true,
57
+ command: () => alert('Delete clicked'),
58
+ },
59
+ ])
60
+
61
+ return { args, menu, showMenu, isLoading, items }
62
+ },
63
+ template: `
64
+ <p @contextmenu="showMenu">Right click on me to open the menu</p>
65
+ <CpContextualMenu :items="items" ref="menu" />
66
+ `,
67
+ }),
68
+ }
@@ -112,7 +112,7 @@ export const Single: Story = {
112
112
  <div style="padding: 20px;">
113
113
  <CpMultiselect v-model="selectedSupplier" v-bind="args" :options="dynamicOptions" :is-loading="isLoading" @search="handleSearch">
114
114
  <template #prefix>
115
- <cp-partner-badge type="supplier" size="xs" />
115
+ <cp-partner-badge type="supplier" size="sm" />
116
116
  </template>
117
117
  <template #option="{ option }">
118
118
  <div style="display: flex; align-items: center; gap: 8px;">
@@ -191,7 +191,7 @@ export const Multiple: Story = {
191
191
  <div style="padding: 20px;">
192
192
  <CpMultiselect v-model="selectedAirlines" v-bind="args" :options="dynamicOptions" :is-loading="isLoading" @search="handleSearch">
193
193
  <template #prefix>
194
- <cp-partner-badge type="airline" size="xs" />
194
+ <cp-partner-badge type="airline" size="sm" />
195
195
  </template>
196
196
  <template #selected-option-leading-icon="{ option }">
197
197
  <cp-airline-logo :iata-code="option.iata_code" size="14" />
@@ -225,7 +225,7 @@ export const Invalid: Story = {
225
225
  <div style="padding: 20px;">
226
226
  <CpMultiselect v-model="selectedSupplier" v-bind="args">
227
227
  <template #prefix>
228
- <cp-partner-badge type="supplier" size="xs" />
228
+ <cp-partner-badge type="supplier" size="sm" />
229
229
  </template>
230
230
  <template #option="{ option }">
231
231
  <div style="display: flex; align-items: center; gap: 8px;">
package/tsconfig.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "isolatedModules": true,
4
+ "moduleResolution": "bundler",
5
+ "module": "esnext",
4
6
  "strict": true,
5
7
  "jsx": "preserve",
6
8
  "jsxImportSource": "vue",