@citizenplane/pimp 9.16.3 → 10.0.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "9.16.3",
3
+ "version": "10.0.0",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8080",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -33,7 +33,6 @@
33
33
  "./*.css": "./dist/*.css"
34
34
  },
35
35
  "dependencies": {
36
- "animejs": "^4.2.2",
37
36
  "feather-icons": "^4.29.2",
38
37
  "floating-vue": "^5.2.2",
39
38
  "luxon": "^3.7.2",
@@ -8,7 +8,7 @@
8
8
  type="chevron-down"
9
9
  />
10
10
  </button>
11
- <transition @enter="enter">
11
+ <transition mode="out-in" name="scale-elastic">
12
12
  <div v-if="isDropdownOpen" ref="dropdownRef" class="cpSelectMenu__dropdown dropdown">
13
13
  <p v-if="dropdownTitle" class="dropdown__title">
14
14
  {{ dropdownTitle }}
@@ -47,7 +47,6 @@
47
47
  </template>
48
48
 
49
49
  <script setup lang="ts">
50
- import { animate, cubicBezier } from 'animejs'
51
50
  import { ref, computed, nextTick } from 'vue'
52
51
 
53
52
  interface SelectValue {
@@ -92,17 +91,6 @@ const inputType = computed(() => {
92
91
  return props.isMultiSelect ? 'checkbox' : 'radio'
93
92
  })
94
93
 
95
- const enter = (): void => {
96
- if (dropdownRef.value) {
97
- animate(dropdownRef.value, {
98
- scale: [0.8, 1],
99
- opacity: [0, 1],
100
- duration: 200,
101
- ease: cubicBezier(0.175, 0.885, 0.32, 1.175),
102
- })
103
- }
104
- }
105
-
106
94
  const toggleDropdown = (): void => {
107
95
  isDropdownOpen.value = !isDropdownOpen.value
108
96
 
@@ -224,6 +224,10 @@ const handleActionClick = (onClick: VoidFunction, closeCallback: VoidFunction) =
224
224
  &__summary {
225
225
  font-weight: 600;
226
226
  color: fn.v(text-accent-primary);
227
+
228
+ &:first-letter {
229
+ text-transform: capitalize;
230
+ }
227
231
  }
228
232
 
229
233
  &__detail {
@@ -362,5 +366,10 @@ const handleActionClick = (onClick: VoidFunction, closeCallback: VoidFunction) =
362
366
  margin: 0 auto;
363
367
  text-align: center;
364
368
  }
369
+
370
+ .cpToast {
371
+ max-width: 100%;
372
+ min-width: 100%;
373
+ }
365
374
  }
366
375
  </style>
@@ -43,7 +43,6 @@ import CpTabs from './CpTabs.vue'
43
43
  import CpTelInput from './CpTelInput.vue'
44
44
  import CpTextarea from './CpTextarea.vue'
45
45
  import CpToast from './CpToast.vue'
46
- import CpToaster from './CpToaster.vue'
47
46
  import CpTooltip from './CpTooltip.vue'
48
47
  import CpTransitionDialog from './CpTransitionDialog.vue'
49
48
  import IconAirline from './icons/IconAirline.vue'
@@ -56,10 +55,8 @@ import IconSupplier from './icons/IconSupplier.vue'
56
55
  import IconThirdParty from './icons/IconThirdParty.vue'
57
56
  import IconTooltip from './icons/IconTooltip.vue'
58
57
  import TransitionExpand from './TransitionExpand.vue'
59
- import createToaster from '@/plugins/toaster'
60
58
 
61
59
  const Components = {
62
- CpToaster,
63
60
  CpToast,
64
61
  CpBadge,
65
62
  CpTabs,
@@ -105,7 +102,7 @@ const Components = {
105
102
  }
106
103
 
107
104
  const Pimp = {
108
- install(app: App, options: Record<string, unknown>) {
105
+ install(app: App) {
109
106
  app.use(PrimeVue, { unstyled: true })
110
107
  app.use(VueTelInput)
111
108
  app.use(ToastService)
@@ -119,12 +116,6 @@ const Pimp = {
119
116
  app.directive('bind-once', BindOnceDirective)
120
117
  app.directive('maska', vMaska)
121
118
  app.directive('tooltip', vTooltip)
122
-
123
- // CpToaster
124
- const methods = createToaster(options)
125
- // @ts-expect-error <comment on type error>
126
- app.$toaster = methods
127
- app.config.globalProperties.$toaster = methods
128
119
  },
129
120
  }
130
121
 
@@ -1,382 +0,0 @@
1
- <template>
2
- <transition @enter="enter" @leave="leave">
3
- <div
4
- v-show="isOpen"
5
- :id="toasterId"
6
- :key="toasterId"
7
- class="cpToaster"
8
- :class="dynamicClass"
9
- role="alert"
10
- @mouseenter="setHoverState()"
11
- @mouseleave="setHoverState(false)"
12
- >
13
- <div class="cpToaster__content">
14
- <cp-icon class="cpToaster__icon" :type="toasterIcon" />
15
- <div class="cpToaster__body">
16
- <cp-heading class="cpToaster__title" :heading-level="HeadingLevels.H4" :size="400">{{ title }}</cp-heading>
17
- <p v-if="description" class="cpToaster__description">{{ description }}</p>
18
- </div>
19
- </div>
20
- <button class="cpToaster__close" type="button" @click="closeToaster">
21
- <cp-icon type="x" />
22
- </button>
23
- <div v-if="actionLabel" class="cpToaster__footer">
24
- <button v-if="actionIsButton" class="cpToaster__button" type="button" @click="handleActionMethod">
25
- {{ actionLabel }}
26
- </button>
27
- <a v-else class="cpToaster__button" v-bind="actionLinkProperties">
28
- {{ actionLabel }}
29
- </a>
30
- </div>
31
- </div>
32
- </transition>
33
- </template>
34
-
35
- <script setup lang="ts">
36
- import { animate, cubicBezier } from 'animejs'
37
- import { ref, computed, watch, onBeforeMount, onMounted, nextTick, getCurrentInstance, useId } from 'vue'
38
-
39
- import CpHeading from '@/components/CpHeading.vue'
40
- import CpIcon from '@/components/CpIcon.vue'
41
-
42
- import { HeadingLevels, Intent } from '@/constants'
43
-
44
- interface Props {
45
- actionAs?: 'link' | 'button'
46
- actionLabel?: string
47
- actionLinkProperties?: Record<string, unknown>
48
- actionMethod?: (vmProperties: Record<string, unknown>) => void
49
- delayBeforeCloseInMs?: number
50
- description?: string
51
- isUnique?: boolean
52
- title: string
53
- type?: string
54
- }
55
-
56
- interface PublicMethods {
57
- closeToaster: () => void
58
- }
59
-
60
- const props = withDefaults(defineProps<Props>(), {
61
- description: '',
62
- type: 'info',
63
- delayBeforeCloseInMs: 5000,
64
- actionLabel: '',
65
- actionLinkProperties: () => ({}),
66
- actionAs: 'button',
67
- actionMethod: () => {},
68
- isUnique: false,
69
- })
70
-
71
- const validateType = (value: string): boolean => {
72
- const intentValues = Object.values(Intent).map((item) => item.value)
73
- return intentValues.includes(value)
74
- }
75
-
76
- if (!validateType(props.type)) {
77
- console.warn(`Type de toaster invalide: ${props.type}`)
78
- }
79
-
80
- const toasterId = useId()
81
- const toastersContainer = ref<HTMLElement | null>(null)
82
- const isOpen = ref<boolean>(false)
83
- const isHovered = ref<boolean>(false)
84
- const deleteCountDown = ref<number>(props.delayBeforeCloseInMs)
85
- const timeoutID = ref<ReturnType<typeof setTimeout>>()
86
- const countDownInterval = ref<ReturnType<typeof setInterval>>()
87
-
88
- const instance = getCurrentInstance()
89
-
90
- const actionIsButton = computed(() => props.actionAs === 'button')
91
-
92
- const toasterIcon = computed(() => {
93
- const intentValues = Object.values(Intent)
94
- const intent = intentValues.find((intentItem) => intentItem.value === props.type)
95
- return intent ? intent.icon : Intent.INFO.icon
96
- })
97
-
98
- const dynamicClass = computed(() => {
99
- return `cpToaster--${props.type || Intent.INFO.value}`
100
- })
101
-
102
- watch(isHovered, (newValue: boolean) => {
103
- if (!newValue) {
104
- handleDeleteEvent()
105
- } else {
106
- cancelDelete()
107
- }
108
- })
109
-
110
- const createContainer = (): void => {
111
- toastersContainer.value = document.querySelector('.cpToaster__container')
112
-
113
- if (toastersContainer.value) return
114
-
115
- if (!toastersContainer.value) {
116
- toastersContainer.value = document.createElement('section')
117
- toastersContainer.value.className = 'cpToaster__container'
118
- }
119
- }
120
-
121
- const setupContainer = (): void => {
122
- if (toastersContainer.value) {
123
- document.body.appendChild(toastersContainer.value)
124
- }
125
- }
126
-
127
- const removeSiblings = (): void => {
128
- if (!props.isUnique) return
129
-
130
- const siblings = document.querySelectorAll('.cpToaster')
131
- siblings.forEach(removeElement)
132
- }
133
-
134
- const showToaster = (): void => {
135
- isOpen.value = true
136
-
137
- nextTick(() => {
138
- if (toastersContainer.value && instance?.proxy?.$el) {
139
- toastersContainer.value.insertAdjacentElement('afterbegin', instance.proxy.$el)
140
- }
141
- })
142
- }
143
-
144
- const setHoverState = (state: boolean = true): void => {
145
- isHovered.value = state
146
- }
147
-
148
- const handleDeleteEvent = (): void => {
149
- startCountDown()
150
-
151
- timeoutID.value = setTimeout(() => {
152
- closeToaster()
153
- }, props.delayBeforeCloseInMs)
154
- }
155
-
156
- const cancelDelete = (): void => {
157
- if (timeoutID.value) {
158
- clearTimeout(timeoutID.value)
159
- }
160
- if (countDownInterval.value) {
161
- clearInterval(countDownInterval.value)
162
- }
163
- resetCountDown()
164
- }
165
-
166
- const startCountDown = (): void => {
167
- countDownInterval.value = setInterval(() => {
168
- deleteCountDown.value -= 1000
169
- if (deleteCountDown.value <= 0 && countDownInterval.value) {
170
- clearInterval(countDownInterval.value)
171
- }
172
- }, 1000)
173
- }
174
-
175
- const resetCountDown = (): void => {
176
- deleteCountDown.value = props.delayBeforeCloseInMs
177
- }
178
-
179
- const handleActionMethod = (): void => {
180
- const vmProperties: Record<string, unknown> = {
181
- closeToaster,
182
- }
183
-
184
- return props.actionMethod(vmProperties)
185
- }
186
-
187
- const closeToaster = (): void => {
188
- cancelDelete()
189
- isOpen.value = false
190
-
191
- setTimeout(() => {
192
- if (instance?.proxy?.$el) {
193
- removeElement(instance.proxy.$el)
194
- }
195
- }, 240)
196
- }
197
-
198
- const removeElement = (el: Element): void => {
199
- if (typeof el.remove !== 'undefined') {
200
- el.remove()
201
- } else if (el.parentNode) {
202
- el.parentNode.removeChild(el)
203
- }
204
- }
205
-
206
- const enter = async (el: Element, done: () => void): Promise<void> => {
207
- animate(el, {
208
- translateY: [-60, 0],
209
- opacity: [0, 1],
210
- duration: 240,
211
- ease: cubicBezier(0.175, 0.885, 0.32, 1.175),
212
- complete: function () {
213
- done()
214
- },
215
- })
216
- }
217
-
218
- const leave = async (el: Element, done: () => void): Promise<void> => {
219
- animate(el, {
220
- scale: [1, 0.8],
221
- opacity: [1, 0],
222
- duration: 240,
223
- ease: cubicBezier(0.0, 0.0, 0.2, 1),
224
- complete: function () {
225
- done()
226
- },
227
- })
228
- }
229
-
230
- onBeforeMount(() => {
231
- createContainer()
232
- setupContainer()
233
- removeSiblings()
234
- })
235
-
236
- onMounted(() => {
237
- showToaster()
238
- handleDeleteEvent()
239
- })
240
-
241
- defineExpose<PublicMethods>({
242
- closeToaster,
243
- })
244
- </script>
245
-
246
- <style lang="scss">
247
- @mixin cp-toaster-style($color, $className) {
248
- &--#{$className} &__icon {
249
- color: $color;
250
- }
251
-
252
- &--#{$className}:before {
253
- background-color: $color;
254
- }
255
- }
256
-
257
- .cpToaster {
258
- position: relative;
259
- box-shadow:
260
- rgba(67, 90, 111, 0.3) 0 0 1px,
261
- rgba(67, 90, 111, 0.47) 0 8px 10px -4px;
262
- background: colors.$neutral-light;
263
- padding: sp.$space-md;
264
- overflow: hidden;
265
- width: max-content;
266
- max-width: 100%;
267
- margin: auto;
268
- pointer-events: auto;
269
- font-size: fn.px-to-rem(14);
270
-
271
- @media (min-width: 769px) {
272
- border-radius: fn.px-to-rem(8);
273
- }
274
-
275
- @media (max-width: 768px) {
276
- min-width: 100%;
277
- }
278
-
279
- &:before {
280
- content: '';
281
- position: absolute;
282
- top: 0;
283
- left: 0;
284
- width: fn.px-to-rem(3);
285
- height: 100%;
286
- }
287
-
288
- &__container {
289
- position: fixed;
290
- z-index: 9999;
291
- top: 0;
292
- left: 0;
293
- right: 0;
294
- margin: auto;
295
- padding: sp.$space-lg;
296
- max-width: fn.px-to-rem(400);
297
- pointer-events: none;
298
-
299
- & > *:not(:last-child) {
300
- margin-bottom: sp.$space;
301
- }
302
-
303
- @media (max-width: 768px) {
304
- max-width: 100%;
305
- }
306
- }
307
-
308
- &__content {
309
- display: flex;
310
- align-items: flex-start;
311
- }
312
-
313
- &__body {
314
- flex: 1;
315
- margin-left: sp.$space;
316
- padding-right: calc(sp.$space-lg + sp.$space-lg);
317
- }
318
-
319
- &__icon {
320
- flex-shrink: 0;
321
- height: fn.px-to-rem(16);
322
- width: fn.px-to-rem(16);
323
- }
324
-
325
- &__content,
326
- &__title {
327
- line-height: fn.px-to-rem(16);
328
- }
329
-
330
- &__title {
331
- font-weight: 600;
332
-
333
- &:not(:only-child) {
334
- margin-bottom: sp.$space-sm;
335
- }
336
- }
337
-
338
- &__description {
339
- font-size: fn.px-to-rem(14);
340
- }
341
-
342
- &__close {
343
- position: absolute;
344
- right: sp.$space;
345
- top: sp.$space;
346
- display: flex;
347
- border-radius: fn.px-to-rem(4);
348
- padding: sp.$space-sm;
349
- color: colors.$neutral-dark-1;
350
-
351
- svg {
352
- margin: 0;
353
- width: fn.px-to-rem(18);
354
- height: fn.px-to-rem(18);
355
- }
356
-
357
- &:hover {
358
- background-color: rgba(colors.$neutral-dark-1, 0.1);
359
- }
360
- }
361
-
362
- &__footer {
363
- display: flex;
364
- justify-content: flex-end;
365
- margin-top: sp.$space-lg;
366
- }
367
-
368
- &__button {
369
- font-size: fn.px-to-rem(12);
370
- color: colors.$secondary-color;
371
-
372
- &:not(:hover) {
373
- text-decoration: underline;
374
- }
375
- }
376
-
377
- @include cp-toaster-style(colors.$secondary-color, 'info');
378
- @include cp-toaster-style(colors.$warning-color, 'warning');
379
- @include cp-toaster-style(colors.$success-color, 'success');
380
- @include cp-toaster-style(colors.$error-color, 'critical');
381
- }
382
- </style>
@@ -1,71 +0,0 @@
1
- import { App, h, render, VNode } from 'vue'
2
-
3
- import CpToaster from '@/components/CpToaster.vue'
4
-
5
- import { Intent } from '@/constants'
6
-
7
- type MountOptions = {
8
- app?: App | null
9
- children?: unknown
10
- element?: HTMLElement | null
11
- props?: Record<string, unknown>
12
- }
13
-
14
- const createElement = () => (typeof document !== 'undefined' ? document.createElement('div') : null)
15
-
16
- const mount = (component: unknown, { props, children, element, app }: MountOptions = {}) => {
17
- let el: HTMLElement | null = element ? element : createElement()
18
-
19
- // @ts-expect-error <comment on type error>
20
- let vNode: VNode = h(component, props, children)
21
- if (app && app._context) {
22
- vNode.appContext = app._context
23
- }
24
-
25
- render(vNode, el as HTMLElement)
26
-
27
- const destroy = () => {
28
- if (el) {
29
- render(null, el)
30
- }
31
- el = null
32
- // @ts-expect-error explicit nulling for GC
33
- vNode = null
34
- }
35
-
36
- return { vNode, destroy, el }
37
- }
38
-
39
- const createToaster = (globalOptions: Record<string, unknown> = {}) => {
40
- return {
41
- show(options: Record<string, unknown> = {}) {
42
- const localOptions = { ...options }
43
-
44
- mount(CpToaster, {
45
- props: { ...globalOptions, ...localOptions },
46
- })
47
- },
48
- unique(options: Record<string, unknown> = {}) {
49
- options.isUnique = true
50
- return this.show(options)
51
- },
52
- success(options: Record<string, unknown> = {}) {
53
- options.type = Intent.SUCCESS.value
54
- return this.show(options)
55
- },
56
- critical(options: Record<string, unknown> = {}) {
57
- options.type = Intent.CRITICAL.value
58
- return this.show(options)
59
- },
60
- info(options: Record<string, unknown> = {}) {
61
- options.type = Intent.INFO.value
62
- return this.show(options)
63
- },
64
- warning(options: Record<string, unknown> = {}) {
65
- options.type = Intent.WARNING.value
66
- return this.show(options)
67
- },
68
- }
69
- }
70
-
71
- export default createToaster