@citizenplane/pimp 9.7.9 → 9.7.12
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/pimp.es.js +1165 -1148
- package/dist/pimp.umd.js +9 -9
- package/package.json +1 -1
- package/src/components/CpMultiselect.vue +9 -13
- package/src/components/CpToaster.vue +15 -4
- package/src/helpers/functions.ts +12 -0
- package/src/stories/CpMultiselect.stories.ts +15 -14
- package/src/stories/CpToaster.stories.ts +52 -12
package/package.json
CHANGED
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
:suggestions="options"
|
|
23
23
|
:typeahead
|
|
24
24
|
@click="handleClick"
|
|
25
|
-
@complete="handleSearch"
|
|
26
25
|
@hide="handleOverlayHidden"
|
|
27
26
|
@keydown.enter="toggleDropdown"
|
|
28
27
|
@keydown.esc.stop
|
|
@@ -80,12 +79,13 @@
|
|
|
80
79
|
<script setup lang="ts">
|
|
81
80
|
import { absolutePosition, getOuterWidth } from '@primeuix/utils/dom'
|
|
82
81
|
import AutoComplete from 'primevue/autocomplete'
|
|
83
|
-
import { ref, computed, onMounted } from 'vue'
|
|
82
|
+
import { ref, computed, onMounted, watch } from 'vue'
|
|
84
83
|
|
|
85
84
|
import BaseInputLabel from '@/components/BaseInputLabel.vue'
|
|
86
85
|
import BaseSelectClearButton from '@/components/BaseSelectClearButton.vue'
|
|
87
86
|
import TransitionExpand from '@/components/TransitionExpand.vue'
|
|
88
87
|
|
|
88
|
+
import { debounce } from '@/helpers/functions'
|
|
89
89
|
import { isEmpty } from '@/helpers/object'
|
|
90
90
|
|
|
91
91
|
interface Emits {
|
|
@@ -94,7 +94,6 @@ interface Emits {
|
|
|
94
94
|
(e: 'update:modelValue', value: Record<string, unknown> | Record<string, unknown>[] | string[] | null): void
|
|
95
95
|
(e: 'overlayShown'): void
|
|
96
96
|
(e: 'overlayHidden'): void
|
|
97
|
-
(e: 'searchChange', value: string | object): void
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
interface Props {
|
|
@@ -145,7 +144,6 @@ const selectModel = computed({
|
|
|
145
144
|
if (typeof value === 'string') {
|
|
146
145
|
return
|
|
147
146
|
}
|
|
148
|
-
|
|
149
147
|
emit('update:modelValue', value)
|
|
150
148
|
},
|
|
151
149
|
})
|
|
@@ -190,18 +188,20 @@ const displayClearButton = computed(() => {
|
|
|
190
188
|
return props.isClearable && !isEmpty(selectModel.value)
|
|
191
189
|
})
|
|
192
190
|
|
|
193
|
-
const handleSearch = (event: { query: string }) => emit('search', event.query)
|
|
194
191
|
const handleClear = () => (selectModel.value = null)
|
|
195
192
|
const handleOverlayShown = () => emit('overlayShown')
|
|
196
193
|
const handleOverlayHidden = () => emit('overlayHidden')
|
|
197
194
|
|
|
198
195
|
const handleValueChange = (newValue: string | object) => {
|
|
199
196
|
if (typeof newValue !== 'string') return
|
|
200
|
-
|
|
201
197
|
searchQuery.value = newValue
|
|
202
|
-
emit('searchChange', newValue)
|
|
203
198
|
}
|
|
204
199
|
|
|
200
|
+
watch(
|
|
201
|
+
searchQuery,
|
|
202
|
+
debounce((newValue) => emit('search', newValue), 500),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
205
|
const getInputElement = () => {
|
|
206
206
|
if (!multiselect.value) return null
|
|
207
207
|
// @ts-expect-error '$el' does not exist on type instance of AutoComplete
|
|
@@ -210,9 +210,7 @@ const getInputElement = () => {
|
|
|
210
210
|
|
|
211
211
|
const selectInputContent = () => {
|
|
212
212
|
const inputElement = getInputElement()
|
|
213
|
-
if (inputElement)
|
|
214
|
-
inputElement.select()
|
|
215
|
-
}
|
|
213
|
+
if (inputElement) inputElement.select()
|
|
216
214
|
}
|
|
217
215
|
|
|
218
216
|
const focusAndSelectInput = () => {
|
|
@@ -245,9 +243,7 @@ const toggleDropdown = () => {
|
|
|
245
243
|
|
|
246
244
|
const handleUpdateModelValue = (value: Record<string, unknown> | string[] | null) => {
|
|
247
245
|
// Autocomplete will set the model value to the query string if not blocked
|
|
248
|
-
if (!value || typeof value === 'string')
|
|
249
|
-
return
|
|
250
|
-
}
|
|
246
|
+
if (!value || typeof value === 'string') return
|
|
251
247
|
}
|
|
252
248
|
|
|
253
249
|
const overrideAlignOverlay = () => {
|
|
@@ -17,11 +17,16 @@
|
|
|
17
17
|
<p v-if="description" class="cpToaster__description">{{ description }}</p>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
|
-
<button class="cpToaster__close" type="button" @click="closeToaster"
|
|
20
|
+
<button class="cpToaster__close" type="button" @click="closeToaster">
|
|
21
|
+
<cp-icon type="x" />
|
|
22
|
+
</button>
|
|
21
23
|
<div v-if="actionLabel" class="cpToaster__footer">
|
|
22
|
-
<button class="cpToaster__button" type="button" @click="handleActionMethod">
|
|
24
|
+
<button v-if="actionIsButton" class="cpToaster__button" type="button" @click="handleActionMethod">
|
|
23
25
|
{{ actionLabel }}
|
|
24
26
|
</button>
|
|
27
|
+
<a v-else class="cpToaster__button" v-bind="actionLinkProperties">
|
|
28
|
+
{{ actionLabel }}
|
|
29
|
+
</a>
|
|
25
30
|
</div>
|
|
26
31
|
</div>
|
|
27
32
|
</transition>
|
|
@@ -37,7 +42,9 @@ import CpIcon from '@/components/CpIcon.vue'
|
|
|
37
42
|
import { HeadingLevels, Intent } from '@/constants'
|
|
38
43
|
|
|
39
44
|
interface Props {
|
|
45
|
+
actionAs?: 'link' | 'button'
|
|
40
46
|
actionLabel?: string
|
|
47
|
+
actionLinkProperties?: Record<string, unknown>
|
|
41
48
|
actionMethod?: (vmProperties: Record<string, unknown>) => void
|
|
42
49
|
delayBeforeCloseInMs?: number
|
|
43
50
|
description?: string
|
|
@@ -55,6 +62,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
55
62
|
type: 'info',
|
|
56
63
|
delayBeforeCloseInMs: 5000,
|
|
57
64
|
actionLabel: '',
|
|
65
|
+
actionLinkProperties: () => ({}),
|
|
66
|
+
actionAs: 'button',
|
|
58
67
|
actionMethod: () => {},
|
|
59
68
|
isUnique: false,
|
|
60
69
|
})
|
|
@@ -78,6 +87,8 @@ const countDownInterval = ref<ReturnType<typeof setInterval>>()
|
|
|
78
87
|
|
|
79
88
|
const instance = getCurrentInstance()
|
|
80
89
|
|
|
90
|
+
const actionIsButton = computed(() => props.actionAs === 'button')
|
|
91
|
+
|
|
81
92
|
const toasterIcon = computed(() => {
|
|
82
93
|
const intentValues = Object.values(Intent)
|
|
83
94
|
const intent = intentValues.find((intentItem) => intentItem.value === props.type)
|
|
@@ -185,8 +196,8 @@ const closeToaster = (): void => {
|
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
const removeElement = (el: Element): void => {
|
|
188
|
-
if (typeof
|
|
189
|
-
|
|
199
|
+
if (typeof el.remove !== 'undefined') {
|
|
200
|
+
el.remove()
|
|
190
201
|
} else if (el.parentNode) {
|
|
191
202
|
el.parentNode.removeChild(el)
|
|
192
203
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
// https://decipher.dev/30-seconds-of-typescript/docs/debounce/
|
|
3
|
+
export function debounce<T extends (...args: any[]) => any>(
|
|
4
|
+
fn: T,
|
|
5
|
+
wait: number = 300,
|
|
6
|
+
): (...args: Parameters<T>) => void {
|
|
7
|
+
let timeout: ReturnType<typeof setTimeout>
|
|
8
|
+
return function (this: any, ...args: Parameters<T>) {
|
|
9
|
+
clearTimeout(timeout)
|
|
10
|
+
timeout = setTimeout(() => fn.apply(this, args), wait)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -4,7 +4,14 @@ import type { Meta, StoryObj } from '@storybook/vue3'
|
|
|
4
4
|
|
|
5
5
|
import CpMultiselect from '@/components/CpMultiselect.vue'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
interface IOption {
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
iata_code?: string
|
|
10
|
+
id: number
|
|
11
|
+
name: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const supplierOptions: IOption[] = [
|
|
8
15
|
{ id: 1, name: 'MGA AIRLINES' },
|
|
9
16
|
{ id: 2, name: 'ZENITH - EUROATLANTIC AIRWAYS' },
|
|
10
17
|
{ id: 3, name: 'ZENITH - SOUTHERN AIRWAYS EXPRESS MOKULELE AIRLINES AND SURF AIR MOBILITY' },
|
|
@@ -18,7 +25,7 @@ const supplierOptions = [
|
|
|
18
25
|
{ id: 11, name: 'UNITRAVEL UTAZÁSI IRODA' },
|
|
19
26
|
]
|
|
20
27
|
|
|
21
|
-
const airlineOptions = [
|
|
28
|
+
const airlineOptions: IOption[] = [
|
|
22
29
|
{ id: 1, name: 'United Airlines', iata_code: 'UA' },
|
|
23
30
|
{ id: 2, name: 'Delta Airlines', iata_code: 'DL' },
|
|
24
31
|
{ id: 3, name: 'American Airlines', iata_code: 'AA' },
|
|
@@ -123,24 +130,21 @@ export const Single: Story = {
|
|
|
123
130
|
|
|
124
131
|
const originalOptions = ref(args.options)
|
|
125
132
|
const dynamicOptions = ref(toValue(originalOptions))
|
|
133
|
+
const selectedSupplier = ref(null)
|
|
126
134
|
|
|
127
135
|
const handleSearch = async (query: string) => {
|
|
128
136
|
isLoading.value = true
|
|
129
137
|
searchQuery.value = query
|
|
130
138
|
|
|
131
|
-
dynamicOptions.value = originalOptions.value
|
|
132
|
-
|
|
133
139
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
134
140
|
|
|
135
|
-
dynamicOptions.value =
|
|
136
|
-
|
|
137
|
-
return option.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
141
|
+
dynamicOptions.value = originalOptions.value?.filter((option) => {
|
|
142
|
+
return (option as IOption).name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
138
143
|
})
|
|
139
144
|
|
|
140
145
|
isLoading.value = false
|
|
141
146
|
}
|
|
142
147
|
|
|
143
|
-
const selectedSupplier = ref(null)
|
|
144
148
|
return { args, selectedSupplier, dynamicOptions, handleSearch, isLoading }
|
|
145
149
|
},
|
|
146
150
|
template: `
|
|
@@ -174,24 +178,21 @@ export const Multiple: Story = {
|
|
|
174
178
|
|
|
175
179
|
const originalOptions = ref(args.options)
|
|
176
180
|
const dynamicOptions = ref(toValue(originalOptions))
|
|
181
|
+
const selectedAirlines = ref([])
|
|
177
182
|
|
|
178
183
|
const handleSearch = async (query: string) => {
|
|
179
184
|
isLoading.value = true
|
|
180
185
|
searchQuery.value = query
|
|
181
186
|
|
|
182
|
-
if (!searchQuery.value) return (dynamicOptions.value = originalOptions.value)
|
|
183
|
-
|
|
184
187
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
185
188
|
|
|
186
|
-
dynamicOptions.value =
|
|
187
|
-
|
|
188
|
-
return option.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
189
|
+
dynamicOptions.value = originalOptions.value?.filter((option) => {
|
|
190
|
+
return (option as IOption).name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
189
191
|
})
|
|
190
192
|
|
|
191
193
|
isLoading.value = false
|
|
192
194
|
}
|
|
193
195
|
|
|
194
|
-
const selectedAirlines = ref([])
|
|
195
196
|
return { args, selectedAirlines, dynamicOptions, handleSearch, isLoading }
|
|
196
197
|
},
|
|
197
198
|
template: `
|
|
@@ -6,6 +6,15 @@ const meta = {
|
|
|
6
6
|
title: 'CpToaster',
|
|
7
7
|
component: CpToaster,
|
|
8
8
|
argTypes: {
|
|
9
|
+
actionAs: {
|
|
10
|
+
control: 'select',
|
|
11
|
+
options: ['link', 'button'],
|
|
12
|
+
description: 'Determines if the action is a link or a button',
|
|
13
|
+
},
|
|
14
|
+
actionLinkProperties: {
|
|
15
|
+
control: 'object',
|
|
16
|
+
description: 'Properties for the action link when actionAs is "link"',
|
|
17
|
+
},
|
|
9
18
|
title: {
|
|
10
19
|
control: 'text',
|
|
11
20
|
description: 'The title of the toast',
|
|
@@ -37,15 +46,17 @@ const meta = {
|
|
|
37
46
|
export default meta
|
|
38
47
|
type Story = StoryObj<typeof meta>
|
|
39
48
|
|
|
49
|
+
const defaultArgs = {
|
|
50
|
+
title: 'Default Toast',
|
|
51
|
+
description: 'This is a default toast message',
|
|
52
|
+
type: 'info',
|
|
53
|
+
delayBeforeCloseInMs: 3000,
|
|
54
|
+
actionLabel: '',
|
|
55
|
+
isUnique: false,
|
|
56
|
+
}
|
|
57
|
+
|
|
40
58
|
export const Default: Story = {
|
|
41
|
-
args:
|
|
42
|
-
title: 'Default Toast',
|
|
43
|
-
description: 'This is a default toast message',
|
|
44
|
-
type: 'info',
|
|
45
|
-
delayBeforeCloseInMs: 3000,
|
|
46
|
-
actionLabel: '',
|
|
47
|
-
isUnique: false,
|
|
48
|
-
},
|
|
59
|
+
args: defaultArgs,
|
|
49
60
|
render: (args) => ({
|
|
50
61
|
template: `
|
|
51
62
|
<div style="padding: 20px;">
|
|
@@ -61,6 +72,7 @@ export const Default: Story = {
|
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
export const DifferentTypes: Story = {
|
|
75
|
+
args: defaultArgs,
|
|
64
76
|
render: () => ({
|
|
65
77
|
template: `
|
|
66
78
|
<div style="padding: 20px; display: flex; flex-direction: column; gap: 16px;">
|
|
@@ -103,7 +115,35 @@ export const DifferentTypes: Story = {
|
|
|
103
115
|
}),
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
export const
|
|
118
|
+
export const WithActionAsLink: Story = {
|
|
119
|
+
args: defaultArgs,
|
|
120
|
+
render: () => ({
|
|
121
|
+
template: `
|
|
122
|
+
<div style="padding: 20px;">
|
|
123
|
+
<cp-button @click="addLinkToaster">Show Toast with Action</cp-button>
|
|
124
|
+
</div>
|
|
125
|
+
`,
|
|
126
|
+
methods: {
|
|
127
|
+
addLinkToaster() {
|
|
128
|
+
this.$toaster.success({
|
|
129
|
+
title: 'This is a success toaster',
|
|
130
|
+
description: 'Description of a toaster with a link',
|
|
131
|
+
actionAs: 'link',
|
|
132
|
+
actionLabel: 'See flight information',
|
|
133
|
+
actionLinkProperties: {
|
|
134
|
+
href: 'http://app.citizenplane.com',
|
|
135
|
+
target: '_blank',
|
|
136
|
+
rel: 'noopener noreferrer',
|
|
137
|
+
},
|
|
138
|
+
isUnique: true,
|
|
139
|
+
})
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const WithActionAsButton: Story = {
|
|
146
|
+
args: defaultArgs,
|
|
107
147
|
render: () => ({
|
|
108
148
|
template: `
|
|
109
149
|
<div style="padding: 20px;">
|
|
@@ -117,9 +157,8 @@ export const WithAction: Story = {
|
|
|
117
157
|
description: 'Description of a toaster with a link',
|
|
118
158
|
actionLabel: 'See flight information',
|
|
119
159
|
isUnique: true,
|
|
120
|
-
actionMethod: (
|
|
121
|
-
|
|
122
|
-
window.open('http://app.citizenplane.com', '_blank')
|
|
160
|
+
actionMethod: () => {
|
|
161
|
+
alert('Action button clicked!')
|
|
123
162
|
},
|
|
124
163
|
})
|
|
125
164
|
},
|
|
@@ -128,6 +167,7 @@ export const WithAction: Story = {
|
|
|
128
167
|
}
|
|
129
168
|
|
|
130
169
|
export const CustomDuration: Story = {
|
|
170
|
+
args: defaultArgs,
|
|
131
171
|
render: () => ({
|
|
132
172
|
template: `
|
|
133
173
|
<div style="padding: 20px;">
|