@energie360/ui-library 0.1.25 → 0.1.27

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.
Files changed (27) hide show
  1. package/components/card/u-card.vue +15 -2
  2. package/components/card-amount-illustrated/card-amount-illustrated.scss +3 -2
  3. package/components/card-amount-illustrated/u-card-amount-illustrated.vue +57 -31
  4. package/components/card-group/card-group.scss +7 -5
  5. package/components/card-group/u-card-group.vue +4 -55
  6. package/components/index.js +3 -0
  7. package/components/notification-item/notification-item.scss +115 -0
  8. package/components/notification-item/u-notification-item.vue +46 -0
  9. package/components/notification-list/notification-list.scss +37 -0
  10. package/components/notification-list/u-notification-list.vue +51 -0
  11. package/components/sprite-animation/u-sprite-animation.vue +11 -1
  12. package/components/sticky-cta/u-sticky-cta.scss +20 -0
  13. package/components/sticky-cta/u-sticky-cta.vue +17 -0
  14. package/components/table/table-row.scss +7 -9
  15. package/components/table/table.scss +6 -0
  16. package/components/table/u-table-cell.vue +1 -1
  17. package/components/table/u-table-row.vue +16 -1
  18. package/components/table/u-table.vue +11 -1
  19. package/i18n/i18n.ts +4 -0
  20. package/layout/index.js +1 -0
  21. package/layout/portal-block/portal-block.scss +8 -0
  22. package/layout/portal-block/u-portal-block.vue +16 -0
  23. package/layout/portal-main/portal-main.scss +1 -0
  24. package/layout/responsive-container/u-responsive-container.vue +27 -3
  25. package/modules/navigation-toolbar-side/navigation-toolbar-side.scss +43 -12
  26. package/modules/navigation-toolbar-side/u-navigation-toolbar-side.vue +10 -7
  27. package/package.json +6 -6
@@ -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,6 @@ 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'
71
+ export { default as UNotificationItem } from './notification-item/u-notification-item.vue'
72
+ export { default as UNotificationList } from './notification-list/u-notification-list.vue'
@@ -0,0 +1,115 @@
1
+ @use '../../base/abstracts' as a;
2
+
3
+ .notification-item {
4
+ z-index: 1;
5
+ position: relative;
6
+ container-type: inline-size;
7
+ margin-bottom: -1px;
8
+ }
9
+
10
+ // Unread state
11
+ .notification-item__inner {
12
+ padding: var(--e-space-4);
13
+ border-top: 1px solid var(--e-c-secondary-05-100);
14
+ border-bottom: 1px solid var(--e-c-secondary-05-100);
15
+ background-color: var(--e-c-secondary-05-50);
16
+ display: flex;
17
+ align-items: center;
18
+ transition: background-color a.$trs-default;
19
+ cursor: pointer;
20
+
21
+ &:hover {
22
+ background-color: var(--e-c-secondary-05-100);
23
+ }
24
+
25
+ &:active {
26
+ background-color: var(--e-c-secondary-05-200);
27
+ }
28
+
29
+ @container (width < 740px) {
30
+ flex-wrap: wrap;
31
+ border-radius: var(--e-brd-radius-2);
32
+ border: 1px solid var(--e-c-secondary-05-100);
33
+ row-gap: var(--e-space-2);
34
+
35
+ .notification-item__text {
36
+ order: 1;
37
+ flex: 0 0 100%;
38
+ padding: 0;
39
+ margin-right: 0;
40
+ }
41
+
42
+ .notification-item__dot {
43
+ order: 2;
44
+ }
45
+
46
+ .notification-item__when {
47
+ padding: 3px 0;
48
+ order: 3;
49
+ margin-left: 0;
50
+ }
51
+
52
+ .notification-item__remove {
53
+ order: 4;
54
+ margin-left: auto;
55
+ }
56
+ }
57
+ }
58
+
59
+ // Read state
60
+ .notification-item.read {
61
+ z-index: 0;
62
+
63
+ .notification-item__inner {
64
+ background-color: var(--e-c-mono-00);
65
+ border-color: var(--e-c-mono-100);
66
+
67
+ &:hover {
68
+ background-color: var(--e-c-primary-01-50);
69
+ }
70
+
71
+ &:active {
72
+ background-color: var(--e-c-primary-01-100);
73
+ }
74
+ }
75
+ }
76
+
77
+ .notification-item__text {
78
+ @include a.type(100);
79
+
80
+ margin-right: var(--e-space-2);
81
+ min-height: 24px;
82
+ padding: 1px 0;
83
+ }
84
+
85
+ .notification-item__dot {
86
+ display: block;
87
+ width: 8px;
88
+ height: 8px;
89
+ border-radius: 100%;
90
+ margin-right: var(--e-space-2);
91
+ background-color: var(--e-c-secondary-05-500);
92
+ }
93
+
94
+ .notification-item__when {
95
+ @include a.type(50);
96
+
97
+ white-space: nowrap;
98
+ color: var(--e-c-mono-700);
99
+ margin-left: auto;
100
+ }
101
+
102
+ .notification-item__remove {
103
+ cursor: pointer;
104
+ z-index: 1;
105
+ color: var(--e-c-primary-01-700);
106
+ margin-left: var(--e-space-6);
107
+
108
+ &:hover {
109
+ color: var(--e-c-secondary-01-900);
110
+ }
111
+ }
112
+
113
+ .visually-hidden {
114
+ @include a.visually-hidden;
115
+ }
@@ -0,0 +1,46 @@
1
+ <script lang="ts" setup>
2
+ import { UIcon } from '../../elements'
3
+ import { getTranslation } from '../../utils/translations/translate'
4
+
5
+ interface Props {
6
+ read: boolean
7
+ when: string
8
+ text?: string
9
+ removable: boolean
10
+ }
11
+
12
+ defineProps<Props>()
13
+ const emits = defineEmits(['remove'])
14
+
15
+ const onRemove = () => {
16
+ emits('remove')
17
+ }
18
+ </script>
19
+
20
+ <template>
21
+ <div :class="['notification-item', { read, removable }]">
22
+ <div class="notification-item__inner">
23
+ <div v-if="!read" class="notification-item__dot"></div>
24
+
25
+ <div class="notification-item__text">
26
+ <slot><div v-html="text" /></slot>
27
+ </div>
28
+
29
+ <div class="notification-item__when">
30
+ {{ when }}
31
+ </div>
32
+
33
+ <div
34
+ v-if="removable"
35
+ class="notification-item__remove"
36
+ role="button"
37
+ @click.prevent.stop="onRemove"
38
+ >
39
+ <span class="visually-hidden">{{ getTranslation('removeNotification') }}</span>
40
+ <UIcon name="delete" />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <style lang="scss" scoped src="./notification-item.scss"></style>
@@ -0,0 +1,37 @@
1
+ @use '../../base/abstracts' as a;
2
+
3
+ .notification-list {
4
+ container-type: inline-size;
5
+
6
+ &.is-empty {
7
+ // This vertical centered style only works in the portal-main layout.
8
+ // It's not really possible to do this "correctly" at the moment, because `portal-main__content` is `display: block`.
9
+ // With `display: flex` it would work, but this would need some refactoring.
10
+ position: relative;
11
+ top: 50%;
12
+ transform: translateY(-100%);
13
+ }
14
+ }
15
+
16
+ .notification-list__inner {
17
+ display: flex;
18
+ flex-direction: column;
19
+
20
+ @container (width < 740px) {
21
+ gap: var(--e-space-3);
22
+ }
23
+ }
24
+
25
+ .notification-list__empty-wrapper {
26
+ text-align: center;
27
+ }
28
+
29
+ .notification-list__empty_animation {
30
+ width: 120px;
31
+ height: 120px;
32
+ margin: 0 auto var(--e-space-6);
33
+ }
34
+
35
+ .notification-list__empty-text {
36
+ @include a.type(300, strong);
37
+ }
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import { DotLottieVue } from '@lottiefiles/dotlottie-vue'
3
+ import { ref, onMounted, useTemplateRef } from 'vue'
4
+
5
+ interface Props {
6
+ emptyText: string
7
+ }
8
+
9
+ defineProps<Props>()
10
+
11
+ const innerEl = useTemplateRef('inner')
12
+ const isEmpty = ref(false)
13
+ const checkItems = () => {
14
+ const itemCount = innerEl.value.children.length
15
+
16
+ isEmpty.value = itemCount === 0
17
+ }
18
+
19
+ onMounted(() => {
20
+ checkItems()
21
+
22
+ const observer = new window.MutationObserver(() => {
23
+ checkItems()
24
+ })
25
+
26
+ observer.observe(innerEl.value, { childList: true, subtree: true })
27
+ })
28
+ </script>
29
+
30
+ <template>
31
+ <div :class="['notification-list', { 'is-empty': isEmpty }]">
32
+ <div ref="inner" class="notification-list__inner">
33
+ <slot />
34
+ </div>
35
+
36
+ <div v-if="isEmpty" class="notification-list__empty-wrapper">
37
+ <div class="notification-list__empty_animation">
38
+ <DotLottieVue
39
+ autoplay
40
+ loop
41
+ src="/static/ui-assets/lottie/notification-empty.lottie"
42
+ style="height: 100%; width: 100%"
43
+ />
44
+ </div>
45
+
46
+ <p class="notification-list__empty-text">{{ emptyText }}</p>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <style lang="scss" scoped src="./notification-list.scss"></style>
@@ -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>
package/i18n/i18n.ts CHANGED
@@ -23,6 +23,7 @@ const translations = {
23
23
  download: 'Herunterladen',
24
24
  like: 'Gefällt mir',
25
25
  dislike: 'Gefällt mir nicht',
26
+ removeNotification: 'Benachrichtigung entfernen',
26
27
  },
27
28
  FR: {
28
29
  yes: 'Ja',
@@ -48,6 +49,7 @@ const translations = {
48
49
  download: 'Télécharger',
49
50
  like: "J'aime ça",
50
51
  dislike: "je ne l'aime pas",
52
+ removeNotification: 'supprimer la notification',
51
53
  },
52
54
  IT: {
53
55
  yes: 'Ja',
@@ -73,6 +75,7 @@ const translations = {
73
75
  download: 'scaricamento',
74
76
  like: 'Mi piace',
75
77
  dislike: 'Non mi piace',
78
+ removeNotification: 'rimuovere la notifica',
76
79
  },
77
80
  EN: {
78
81
  yes: 'Ja',
@@ -98,6 +101,7 @@ const translations = {
98
101
  download: 'Download',
99
102
  like: 'I like it',
100
103
  dislike: 'I do not like it',
104
+ removeNotification: 'remove notification',
101
105
  },
102
106
  }
103
107
 
package/layout/index.js CHANGED
@@ -4,3 +4,4 @@ export { default as UPortalContentAside } from './portal-content-aside/u-portal-
4
4
  export { default as UTileGrid } from './tile-grid/u-tile-grid.vue'
5
5
  export { default as UTileItem } from './tile-grid/u-tile-item.vue'
6
6
  export { default as UResponsiveContainer } from './responsive-container/u-responsive-container.vue'
7
+ export { default as UPortalBlock } from './portal-block/u-portal-block.vue'
@@ -0,0 +1,8 @@
1
+ .portal-block {
2
+ // default 'small'
3
+ margin-bottom: var(--e-space-6);
4
+
5
+ &.big {
6
+ margin-bottom: var(--e-space-10);
7
+ }
8
+ }
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ big?: boolean
4
+ space?: 'small' | 'big'
5
+ }
6
+
7
+ const { space = 'small' } = defineProps<Props>()
8
+ </script>
9
+
10
+ <template>
11
+ <div :class="['portal-block', space]">
12
+ <slot />
13
+ </div>
14
+ </template>
15
+
16
+ <style scoped lang="scss" src="./portal-block.scss"></style>
@@ -81,6 +81,7 @@
81
81
  .portal-main__content {
82
82
  padding-top: var(--e-space-10);
83
83
  padding-bottom: var(--e-space-10);
84
+ flex: 1 1 100%;
84
85
 
85
86
  @include c.portal-content-container;
86
87
 
@@ -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)};
@@ -15,7 +15,13 @@
15
15
  width: a.rem(88);
16
16
 
17
17
  .navigation-toolbar-side__logo {
18
- width: a.rem(40);
18
+ .mini {
19
+ opacity: 1;
20
+ }
21
+
22
+ .large {
23
+ opacity: 0;
24
+ }
19
25
  }
20
26
 
21
27
  .navigation-toolbar-side__nav-links,
@@ -29,6 +35,11 @@
29
35
  width: a.rem(196);
30
36
  height: a.rem(40);
31
37
  margin-bottom: var(--e-space-12);
38
+
39
+ .mini {
40
+ position: absolute;
41
+ opacity: 0;
42
+ }
32
43
  }
33
44
 
34
45
  .navigation-toolbar-side__nav-links {
@@ -49,12 +60,6 @@
49
60
  width: 100%;
50
61
  }
51
62
 
52
- .navigation-toolbar-side__top-bar-logo {
53
- img {
54
- height: a.rem(24);
55
- }
56
- }
57
-
58
63
  .navigation-toolbar-side__top-bar-ctas {
59
64
  display: flex;
60
65
  column-gap: var(--e-space-4);
@@ -62,28 +67,54 @@
62
67
 
63
68
  // Animation
64
69
  .navigation-toolbar-side {
70
+ --duration: var(--e-trs-duration-faster);
71
+
65
72
  &.is-collapsing {
66
73
  will-change: width;
67
- transition: width var(--e-trs-duration-faster) var(--transition-ease-out);
74
+ transition: width var(--duration) var(--transition-ease-out);
68
75
  width: a.rem(88);
69
76
  overflow: hidden;
70
77
 
71
78
  .navigation-toolbar-side__nav-links,
72
79
  .navigation-toolbar-side__menu-ctas {
73
- transition: padding var(--e-trs-duration-faster) var(--transition-ease-out);
80
+ transition: padding var(--duration) var(--transition-ease-out);
74
81
  padding: 0 var(--e-space-0_5);
75
82
  }
83
+
84
+ .navigation-toolbar-side__logo {
85
+ .mini {
86
+ opacity: 1;
87
+ transition: opacity var(--duration) var(--transition-ease-out);
88
+ }
89
+
90
+ .large {
91
+ opacity: 0;
92
+ transition: opacity var(--duration) var(--transition-ease-out);
93
+ }
94
+ }
76
95
  }
77
96
 
78
97
  &.is-expanding {
79
98
  will-change: width;
80
- transition: all var(--e-trs-duration-faster) var(--transition-ease-out);
99
+ transition: all var(--duration) var(--transition-ease-out);
81
100
  width: var(--nav-width-expanded);
82
101
 
83
102
  .navigation-toolbar-side__nav-links,
84
103
  .navigation-toolbar-side__menu-ctas {
85
- transition: padding var(--e-trs-duration-faster) var(--transition-ease-out);
104
+ transition: padding var(--duration) var(--transition-ease-out);
86
105
  padding: 0;
87
106
  }
107
+
108
+ .navigation-toolbar-side__logo {
109
+ .mini {
110
+ opacity: 0;
111
+ transition: opacity var(--duration) var(--transition-ease-out);
112
+ }
113
+
114
+ .large {
115
+ opacity: 1;
116
+ transition: opacity var(--duration) var(--transition-ease-out);
117
+ }
118
+ }
88
119
  }
89
120
  }
@@ -61,13 +61,16 @@ watch(
61
61
  },
62
62
  ]"
63
63
  >
64
- <a class="navigation-toolbar-side__logo" :href="logoLink.href" :target="logoLink.target">
65
- <img
66
- v-if="isCollapsed || isExpanding"
67
- :src="logoMinifiedImage.src"
68
- :alt="logoMinifiedImage.alt"
69
- />
70
- <img v-else :src="logoImage.src" :alt="logoImage.alt" />
64
+ <a
65
+ :class="[
66
+ 'navigation-toolbar-side__logo',
67
+ { 'is-collapsing': isCollapsing, 'is-expanding': isExpanding },
68
+ ]"
69
+ :href="logoLink.href"
70
+ :target="logoLink.target"
71
+ >
72
+ <img class="mini" :src="logoMinifiedImage.src" :alt="logoMinifiedImage.alt" />
73
+ <img class="large" :src="logoImage.src" :alt="logoImage.alt" />
71
74
  </a>
72
75
 
73
76
  <nav ref="mobile-panel" class="navigation-toolbar-side__nav-panel">
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.27",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,17 +25,17 @@
25
25
  "license": "MIT",
26
26
  "devDependencies": {
27
27
  "@tsconfig/node22": "^22.0.2",
28
- "@types/node": "^22.16.5",
28
+ "@types/node": "^22.19.0",
29
29
  "@vue/tsconfig": "^0.7.0",
30
- "autoprefixer": "^10.4.21",
30
+ "autoprefixer": "^10.4.22",
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.94.0",
34
+ "typescript": "^5.9.3"
35
35
  },
36
36
  "dependencies": {
37
+ "@lottiefiles/dotlottie-vue": "^0.10.7",
37
38
  "@lottiefiles/lottie-player": "^2.0.12",
38
- "swiper": "^11.2.10",
39
39
  "@energie360/design-tokens": "^1.3.0"
40
40
  },
41
41
  "peerDependencies": {