@energie360/ui-library 0.1.25 → 0.1.26

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.
@@ -12,7 +12,16 @@ const toggleActiveCard = (v: boolean) => {
12
12
  isActive.value = v
13
13
  }
14
14
 
15
- provide('card', { toggleActiveCard })
15
+ const isHovering = ref(false)
16
+
17
+ const onMouseenter = () => {
18
+ isHovering.value = true
19
+ }
20
+ const onMouseleave = () => {
21
+ isHovering.value = false
22
+ }
23
+
24
+ provide('card', { toggleActiveCard, isHovering })
16
25
 
17
26
  watch(
18
27
  () => active,
@@ -23,7 +32,11 @@ watch(
23
32
  </script>
24
33
 
25
34
  <template>
26
- <section :class="['card', { 'is-active': isActive }]">
35
+ <section
36
+ :class="['card', { 'is-active': isActive }]"
37
+ @mouseenter="onMouseenter"
38
+ @mouseleave="onMouseleave"
39
+ >
27
40
  <slot></slot>
28
41
  </section>
29
42
  </template>
@@ -27,9 +27,10 @@
27
27
  right: 0;
28
28
  aspect-ratio: 5/2;
29
29
  width: 85px;
30
+ z-index: 1;
30
31
  }
31
32
 
32
33
  .card-amount-illustrated__slider {
33
- margin-top: -2px;
34
- margin-bottom: -2px;
34
+ margin-top: -3px;
35
+ margin-bottom: -3px;
35
36
  }
@@ -1,36 +1,73 @@
1
1
  <script setup lang="ts">
2
- import { SpriteAnimation } from '../sprite-animation/u-sprite-animation.vue'
3
- import { UProgressBar, USpriteAnimation } from '../'
4
- import { useTemplateRef } from 'vue'
2
+ import { UProgressBar } from '../'
3
+ import { useTemplateRef, inject, watch, ref, onMounted, onUnmounted } from 'vue'
5
4
  import { Image } from '../../elements/types'
5
+ import '@lottiefiles/lottie-player'
6
6
 
7
7
  interface Props {
8
8
  title?: string
9
9
  amount?: number
10
10
  maxAmountImage: Image
11
- spriteAnimation?: Pick<
12
- SpriteAnimation,
13
- 'spritesheetPath' | 'framePositions' | 'frameHeight' | 'frameWidth'
14
- >
11
+ lottieSrc: string
15
12
  }
16
13
 
17
- defineProps<Props>()
14
+ const { lottieSrc, title = '', amount = -1 } = defineProps<Props>()
18
15
  defineEmits(['amount'])
16
+ const { isHovering: cardIsHovering } = inject('card', {})
19
17
 
20
- const spriteAnimRef = useTemplateRef('spriteAnim')
21
- let pausedFrame: number
22
-
23
- const onPause = (frame: number) => {
24
- pausedFrame = frame
25
- }
18
+ const lottieWrapperRef = useTemplateRef('lottie-wrapper')
19
+ let lottieEl
20
+ let lottie
21
+ const lottieReady = ref(false)
22
+ const isHovering = ref(false)
26
23
 
27
24
  const onMouseenter = () => {
28
- spriteAnimRef.value.play()
25
+ if (isHovering.value === true || lottieReady.value === false) {
26
+ return
27
+ }
28
+
29
+ lottie.setDirection(1)
30
+ lottie.play()
31
+ isHovering.value = true
29
32
  }
30
33
 
31
34
  const onMouseleave = () => {
32
- spriteAnimRef.value.pause()
33
- spriteAnimRef.value.playReverse(pausedFrame)
35
+ if (cardIsHovering?.value === true || lottieReady.value === false) {
36
+ return
37
+ }
38
+
39
+ lottie.setDirection(-1)
40
+ lottie.play()
41
+ isHovering.value = false
42
+ }
43
+
44
+ onMounted(() => {
45
+ // lottie-player web-component will be ready in this callback.
46
+ const tmp = document.createElement('lottie-player')
47
+ tmp.setAttribute('src', lottieSrc)
48
+ tmp.setAttribute('speed', '2.5')
49
+ lottieEl = lottieWrapperRef.value.appendChild(tmp)
50
+
51
+ lottieEl.addEventListener('load', () => {
52
+ lottie = lottieEl.getLottie()
53
+ lottieReady.value = true
54
+ })
55
+ })
56
+
57
+ onUnmounted(() => {
58
+ if (lottieEl) {
59
+ lottieEl.remove()
60
+ }
61
+ })
62
+
63
+ if (cardIsHovering) {
64
+ watch(cardIsHovering, (v) => {
65
+ if (v) {
66
+ onMouseenter()
67
+ } else {
68
+ onMouseleave()
69
+ }
70
+ })
34
71
  }
35
72
  </script>
36
73
 
@@ -41,22 +78,11 @@ const onMouseleave = () => {
41
78
  </h3>
42
79
 
43
80
  <div
44
- v-if="$slots.illustration || spriteAnimation"
81
+ ref="lottie-wrapper"
45
82
  class="card-amount-illustrated__illustration"
83
+ @mouseenter="onMouseenter"
84
+ @mouseleave="onMouseleave"
46
85
  >
47
- <slot name="illustration">
48
- <USpriteAnimation
49
- ref="spriteAnim"
50
- v-bind="spriteAnimation"
51
- :duration="200"
52
- :width="200"
53
- :height="200"
54
- @mouseenter="onMouseenter"
55
- @mouseleave="onMouseleave"
56
- @pause="onPause"
57
- ></USpriteAnimation>
58
- </slot>
59
-
60
86
  <div
61
87
  v-if="($slots.maxAmountImage || maxAmountImage) && amount === 100"
62
88
  class="card-amount-illustrated__max-amount-image"
@@ -26,16 +26,18 @@
26
26
  &.columns-4 {
27
27
  grid-template-columns: repeat(4, 1fr);
28
28
  grid-template-rows: 1fr;
29
- }
30
29
 
31
- &.use-carousel {
32
- display: block;
30
+ @include a.bp(xl) {
31
+ grid-template-columns: repeat(2, 1fr);
32
+ }
33
33
 
34
- // touch-action: pan-y;
34
+ @include a.bp(m) {
35
+ grid-template-columns: repeat(1, 1fr);
36
+ }
35
37
  }
36
38
 
37
39
  @include a.bp(lg) {
38
- &:has(> :last-child:nth-child(n)) {
40
+ &:has(> :last-child:nth-child(n)):not(.columns-4) {
39
41
  grid-template-columns: repeat(1, 1fr);
40
42
  grid-template-rows: 1fr;
41
43
  }
@@ -1,48 +1,17 @@
1
1
  <script setup lang="ts">
2
- import { register } from 'swiper/element/bundle'
3
- import { onMounted, onUnmounted, useTemplateRef, watch } from 'vue'
2
+ import { onMounted, onUnmounted, useTemplateRef } from 'vue'
4
3
  import { debounceRaf } from '../../utils/functions/debounce'
5
4
  import { isLarge } from '../../utils/functions/breakpoint'
6
5
  import { useRadioGroup } from '../../elements/radio-group/radio-group-composables'
7
6
 
8
- // Register swiper custom elements.
9
- register()
10
-
11
7
  interface Props {
12
8
  columns?: 1 | 2 | 3 | 4 | 'auto'
13
- useCarousel?: boolean
14
9
  }
15
10
 
16
- const { columns = 'auto', useCarousel = false } = defineProps<Props>()
11
+ const { columns = 'auto' } = defineProps<Props>()
17
12
  const model = defineModel<string>()
18
13
  const group = useTemplateRef('group')
19
14
 
20
- const initSwiper = () => {
21
- if (!useCarousel) {
22
- return
23
- }
24
-
25
- const swiperEl = group.value.querySelector('swiper-container')
26
- const swiperParams = {
27
- spaceBetween: 20,
28
- breakpoints: {
29
- 1240: {
30
- slidesPerView: 4,
31
- },
32
- 1020: {
33
- slidesPerView: 3,
34
- },
35
- 740: {
36
- slidesPerView: 2,
37
- },
38
- },
39
- }
40
-
41
- Object.assign(swiperEl, swiperParams)
42
-
43
- swiperEl.initialize()
44
- }
45
-
46
15
  const onResize = debounceRaf(() => {
47
16
  const headers = Array.from(group.value.querySelectorAll('.card-header'))
48
17
 
@@ -80,8 +49,6 @@ onMounted(() => {
80
49
  // Initial state
81
50
  onResize()
82
51
  })
83
-
84
- initSwiper()
85
52
  })
86
53
 
87
54
  onUnmounted(() => {
@@ -92,29 +59,11 @@ useRadioGroup({
92
59
  model,
93
60
  provideKey: 'card-group',
94
61
  })
95
-
96
- watch(
97
- () => useCarousel,
98
- () => {
99
- initSwiper()
100
- },
101
- )
102
62
  </script>
103
63
 
104
64
  <template>
105
- <div ref="group" :class="['card-group', `columns-${columns}`, { 'use-carousel': useCarousel }]">
106
- <template v-if="useCarousel">
107
- <swiper-container init="false">
108
- <template v-for="(vnode, idx) in $slots.default()[0].children" :key="idx">
109
- <swiper-slide>
110
- <component :is="vnode" />
111
- </swiper-slide>
112
- </template>
113
- </swiper-container>
114
- </template>
115
- <template v-else>
116
- <slot />
117
- </template>
65
+ <div ref="group" :class="['card-group', `columns-${columns}`]">
66
+ <slot />
118
67
  </div>
119
68
  </template>
120
69
 
@@ -67,3 +67,4 @@ export { default as UChip } from './chip/u-chip.vue'
67
67
  export { default as USlider } from './slider/u-slider.vue'
68
68
  export { default as USpriteAnimation } from './sprite-animation/u-sprite-animation.vue'
69
69
  export { default as USliderProgressAnimation } from './slider-progress-animation/u-slider-progress-animation.vue'
70
+ export { default as UStickyCta } from './sticky-cta/u-sticky-cta.vue'
@@ -84,7 +84,7 @@ const durationFromFps = () => (1000 / fps) * framePositions.length
84
84
  const durationFromFrames = (start, end) =>
85
85
  (Math.abs(end - start) / framePositions.length) * duration
86
86
 
87
- const animate = (start, end) => {
87
+ const animate = (start, end, loop = false) => {
88
88
  isAnimating = true
89
89
 
90
90
  animation = animateValue({
@@ -96,6 +96,11 @@ const animate = (start, end) => {
96
96
  toFrame(value)
97
97
  },
98
98
  onComplete(value) {
99
+ if (loop) {
100
+ animate(start, end, loop)
101
+ return
102
+ }
103
+
99
104
  isAnimating = false
100
105
  emits('pause', getFrameFromProgress(value))
101
106
  },
@@ -110,6 +115,10 @@ const play = (start = 0, end = framePositions.length - 1) => {
110
115
  animate(start, end)
111
116
  }
112
117
 
118
+ const playLoop = () => {
119
+ animate(0, framePositions.length - 1, true)
120
+ }
121
+
113
122
  const playReverse = (start = framePositions.length - 1, end = 0) => {
114
123
  if (isAnimating) {
115
124
  animation.abort()
@@ -145,6 +154,7 @@ onMounted(async () => {
145
154
  defineExpose({
146
155
  play,
147
156
  playReverse,
157
+ playLoop,
148
158
  pause,
149
159
  toFrame,
150
160
  })
@@ -0,0 +1,20 @@
1
+ @use '../../base/abstracts' as a;
2
+ @use '../../layout/container/container' as c;
3
+
4
+ .sticky-cta {
5
+ position: sticky;
6
+ bottom: 0;
7
+ padding: var(--e-space-6) 0;
8
+ background-color: var(--e-c-mono-00);
9
+ border-top: 1px solid var(--e-c-mono-200);
10
+ z-index: 1000;
11
+ }
12
+
13
+ .sticky-cta__inner {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ }
17
+
18
+ .container {
19
+ @include c.grid-container;
20
+ }
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts"></script>
2
+
3
+ <template>
4
+ <div class="sticky-cta">
5
+ <div class="container">
6
+ <div class="sticky-cta__inner">
7
+ <div>
8
+ <slot name="info"></slot>
9
+ </div>
10
+
11
+ <slot name="cta"></slot>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </template>
16
+
17
+ <style lang="scss" scoped src="./u-sticky-cta.scss"></style>
@@ -1,20 +1,18 @@
1
1
  @use '../../base/abstracts' as a;
2
2
 
3
- .table-row {
3
+ @mixin table-row {
4
4
  display: table-row;
5
5
  border-bottom: 1px solid var(--e-c-mono-100);
6
+ }
7
+
8
+ .table-row {
9
+ @include table-row;
6
10
 
7
11
  &.highlight {
8
12
  background-color: var(--e-c-mono-50);
9
13
  }
10
14
 
11
- &.mobile-layout-card {
12
- @include a.bp(m) {
13
- display: grid;
14
- gap: var(--e-space-2);
15
- padding: var(--e-space-4);
16
- border: 1px solid var(--e-c-mono-200);
17
- border-radius: var(--e-brd-radius-2);
18
- }
15
+ &.dark {
16
+ background-color: var(--e-c-mono-100);
19
17
  }
20
18
  }
@@ -1,5 +1,11 @@
1
1
  @use '../../base/abstracts' as a;
2
2
 
3
+ .table-title {
4
+ @include a.type(200, strong);
5
+
6
+ padding-left: var(--e-space-4);
7
+ }
8
+
3
9
  .table {
4
10
  display: table;
5
11
  border-collapse: collapse;
@@ -24,7 +24,7 @@ const {
24
24
  :class="['table-cell', `h-align-${hAlign}`, `v-align-${vAlign}`, { 'has-tooltip': infoText }]"
25
25
  >
26
26
  <div :class="['cell-content', `text-${textStyle}`, { nowrap }]">
27
- <slot>{{ text }}</slot>
27
+ <slot><span v-html="text" /></slot>
28
28
  </div>
29
29
 
30
30
  <div v-if="infoText" class="info-tooltip">
@@ -1,13 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  interface Props {
3
3
  highlight?: boolean
4
+ dark?: boolean
4
5
  }
5
6
 
6
7
  defineProps<Props>()
7
8
  </script>
8
9
 
9
10
  <template>
10
- <div role="row" :class="['table-row', { highlight }]">
11
+ <div role="row" :class="['table-row', { highlight, dark }]">
11
12
  <slot></slot>
12
13
  </div>
13
14
  </template>
@@ -15,3 +16,17 @@ defineProps<Props>()
15
16
  <style scoped lang="scss">
16
17
  @use './table-row.scss';
17
18
  </style>
19
+
20
+ <style lang="scss">
21
+ @use './table-row.scss' as tr;
22
+
23
+ .table-row-linked {
24
+ @include tr.table-row;
25
+
26
+ transition: background var(--e-trs-duration-faster) var(--e-trs-easing-default);
27
+
28
+ &:hover {
29
+ background-color: var(--e-c-primary-01-50);
30
+ }
31
+ }
32
+ </style>
@@ -1,6 +1,16 @@
1
+ <script lang="ts" setup>
2
+ interface Props {
3
+ role?: string
4
+ title?: string
5
+ }
6
+
7
+ const { role = 'table', title = '' } = defineProps<Props>()
8
+ </script>
9
+
1
10
  <template>
2
11
  <div class="table-scroll-wrapper">
3
- <div role="table" class="table">
12
+ <p v-if="title" class="table-title">{{ title }}</p>
13
+ <div :role="role" class="table">
4
14
  <slot></slot>
5
15
  </div>
6
16
  </div>
@@ -1,8 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue'
3
+ import { debounceRaf } from '../../utils/functions/debounce'
3
4
 
4
5
  interface Props {
5
6
  breakpoint: number
7
+ type?: 'viewport' | 'container'
6
8
  }
7
9
 
8
10
  const root = useTemplateRef('root')
@@ -11,10 +13,22 @@ const resizeObserver = new ResizeObserver((entries) => {
11
13
  containerWidth.value = entries[0].contentBoxSize[0].inlineSize
12
14
  })
13
15
 
14
- defineProps<Props>()
16
+ const { type = 'viewport' } = defineProps<Props>()
17
+
18
+ const onResize = debounceRaf(() => {
19
+ containerWidth.value = window.innerWidth
20
+ })
15
21
 
16
22
  onMounted(() => {
17
- resizeObserver.observe(root.value)
23
+ switch (type) {
24
+ case 'container':
25
+ resizeObserver.observe(root.value)
26
+ break
27
+ case 'viewport':
28
+ default:
29
+ onResize()
30
+ window.addEventListener('resize', onResize)
31
+ }
18
32
  })
19
33
 
20
34
  onUnmounted(() => {
@@ -23,7 +37,7 @@ onUnmounted(() => {
23
37
  </script>
24
38
 
25
39
  <template>
26
- <div ref="root" class="responsive-container">
40
+ <div v-if="type === 'container'" ref="root" class="responsive-container">
27
41
  <template v-if="containerWidth >= breakpoint">
28
42
  <slot name="above"></slot>
29
43
  </template>
@@ -32,4 +46,14 @@ onUnmounted(() => {
32
46
  <slot name="below"></slot>
33
47
  </template>
34
48
  </div>
49
+
50
+ <template v-else>
51
+ <template v-if="containerWidth >= breakpoint">
52
+ <slot name="above"></slot>
53
+ </template>
54
+
55
+ <template v-else>
56
+ <slot name="below"></slot>
57
+ </template>
58
+ </template>
35
59
  </template>
@@ -1,4 +1,4 @@
1
- @use '../../base/abstracts/' as a;
1
+ @use '../../base/abstracts' as a;
2
2
 
3
3
  .navigation-toolbar-side {
4
4
  --nav-width-expanded: #{a.rem(280)};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@energie360/ui-library",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,13 +25,13 @@
25
25
  "license": "MIT",
26
26
  "devDependencies": {
27
27
  "@tsconfig/node22": "^22.0.2",
28
- "@types/node": "^22.16.5",
28
+ "@types/node": "^22.18.8",
29
29
  "@vue/tsconfig": "^0.7.0",
30
30
  "autoprefixer": "^10.4.21",
31
31
  "chokidar": "^4.0.3",
32
32
  "postcss": "^8.5.6",
33
- "sass": "^1.89.2",
34
- "typescript": "^5.8.3"
33
+ "sass": "^1.93.2",
34
+ "typescript": "^5.9.3"
35
35
  },
36
36
  "dependencies": {
37
37
  "@lottiefiles/lottie-player": "^2.0.12",