@energie360/ui-library 0.1.22 → 0.1.24

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 (38) hide show
  1. package/components/badge/u-badge.vue +2 -2
  2. package/components/card-amount/card-amount.scss +7 -0
  3. package/components/card-amount/u-card-amount.vue +21 -0
  4. package/components/card-amount-illustrated/card-amount-illustrated.scss +35 -0
  5. package/components/card-amount-illustrated/u-card-amount-illustrated.vue +80 -0
  6. package/components/card-contact/card-contact.scss +5 -3
  7. package/components/card-contact/u-card-contact.vue +15 -10
  8. package/components/card-footer/u-card-footer.vue +6 -2
  9. package/components/card-group/card-group.scss +25 -11
  10. package/components/card-group/u-card-group.vue +59 -4
  11. package/components/chip/chip.scss +31 -4
  12. package/components/chip/u-chip.vue +5 -2
  13. package/components/index.js +5 -0
  14. package/components/progress-bar/progress-bar.scss +5 -9
  15. package/components/progress-bar/u-progress-bar.vue +3 -6
  16. package/components/richtext/richtext.scss +2 -0
  17. package/components/skeleton-loader/u-skeleton-loader.vue +1 -1
  18. package/components/slider/slider.scss +249 -0
  19. package/components/slider/u-slider.vue +174 -0
  20. package/components/slider-progress-animation/slider-progress-animation.scss +25 -0
  21. package/components/slider-progress-animation/u-slider-progress-animation.vue +65 -0
  22. package/components/sprite-animation/sprite-animation.scss +7 -0
  23. package/components/sprite-animation/u-sprite-animation.vue +169 -0
  24. package/components/table/cell-progress-bar.scss +4 -0
  25. package/components/table/u-cell-icon-group.vue +12 -7
  26. package/components/table/u-cell-progress-bar.vue +5 -5
  27. package/components/text-block/text-block.scss +18 -14
  28. package/components/text-block/u-text-block.vue +17 -11
  29. package/components/tooltip/tooltip.scss +4 -2
  30. package/components/tooltip/u-tooltip.vue +20 -2
  31. package/elements/select-chip/select-chip.scss +1 -1
  32. package/elements/select-chip/u-select-chip.vue +7 -6
  33. package/modules/dialog/u-dialog.vue +2 -2
  34. package/package.json +3 -1
  35. package/utils/functions/animate.js +258 -0
  36. package/utils/math/scale-value.js +13 -0
  37. package/wizard/wizard-layout/u-wizard-layout-block.vue +2 -2
  38. package/wizard/wizard-top-bar/u-wizard-top-bar.vue +0 -8
@@ -0,0 +1,249 @@
1
+ @use '../../base/abstracts' as a;
2
+
3
+ @mixin thumb-reset {
4
+ appearance: none;
5
+ border: none;
6
+ height: var(--slider-thumb-height);
7
+ width: var(--slider-thumb-width);
8
+ border-radius: 100%;
9
+ }
10
+
11
+ .slider {
12
+ --slider-height: 60px;
13
+ --slider-width: 100%;
14
+ --slider-track-height: 4px;
15
+ --slider-upper-fill-color: rgb(150 150 150);
16
+ --slider-lower-fill-color: rgb(0 150 0);
17
+ --slider-thumb-height: 60px;
18
+ --slider-thumb-width: 60px;
19
+
20
+ position: relative;
21
+ display: inline-flex;
22
+ align-items: center;
23
+ column-gap: var(--e-space-4);
24
+ width: var(--slider-width);
25
+ height: var(--slider-height);
26
+
27
+ // Style native input
28
+ input::-moz-range-thumb {
29
+ @include thumb-reset;
30
+ }
31
+
32
+ input::-webkit-slider-thumb {
33
+ @include thumb-reset;
34
+ }
35
+
36
+ // States
37
+ &.is-sliding {
38
+ .slider__dot,
39
+ .slider__dot-wrapper {
40
+ pointer-events: none;
41
+ }
42
+ }
43
+
44
+ &.is-sliding.is-touch {
45
+ .slider__thumb {
46
+ transform: translateX(-50%) translateY(-50%);
47
+ }
48
+ }
49
+ }
50
+
51
+ .slider__min-value,
52
+ .slider__max-value {
53
+ @include a.type(200);
54
+
55
+ flex: 0 0 auto;
56
+ }
57
+
58
+ .slider__slider {
59
+ flex: 1 0 auto;
60
+ position: relative;
61
+ height: 100%;
62
+ }
63
+
64
+ .slider__input {
65
+ position: absolute;
66
+ top: 0;
67
+ left: calc(var(--slider-thumb-width) / -2);
68
+ right: calc(var(--slider-thumb-width) / -2);
69
+ height: 100%;
70
+ pointer-events: none;
71
+
72
+ input {
73
+ width: 100%;
74
+ height: 100%;
75
+ }
76
+ }
77
+
78
+ .slider__control {
79
+ width: 100%;
80
+ height: 100%;
81
+ touch-action: none;
82
+
83
+ &::before {
84
+ content: '';
85
+ position: absolute;
86
+ top: calc(50% - var(--slider-track-height) / 2);
87
+ height: var(--slider-track-height);
88
+ width: 100%;
89
+ background-color: var(--e-c-primary-01-100);
90
+ border-radius: var(--e-brd-radius-1);
91
+ }
92
+ }
93
+
94
+ .slider__dot {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ width: 100%;
99
+ height: 100%;
100
+ border-radius: 100%;
101
+ background-color: var(--e-c-mono-00);
102
+ border: 2px solid var(--e-c-primary-01-500);
103
+ pointer-events: auto;
104
+ cursor: pointer;
105
+
106
+ &::before {
107
+ content: '';
108
+ position: absolute;
109
+ display: block;
110
+ width: a.rem(8);
111
+ height: a.rem(8);
112
+ border-radius: 100%;
113
+ background-color: var(--e-c-primary-01-500);
114
+ }
115
+
116
+ &:hover {
117
+ &::before {
118
+ background-color: var(--e-c-primary-01-700);
119
+ }
120
+ }
121
+
122
+ &:active {
123
+ border-color: var(--e-c-primary-01-700);
124
+ }
125
+
126
+ // Dot variants
127
+ &.upper {
128
+ border-color: var(--e-c-mono-200);
129
+
130
+ &::before {
131
+ width: a.rem(4);
132
+ height: a.rem(4);
133
+ }
134
+
135
+ &:hover {
136
+ &::before {
137
+ width: a.rem(8);
138
+ height: a.rem(8);
139
+ }
140
+ }
141
+
142
+ &:active {
143
+ border-color: var(--e-c-primary-01-700);
144
+ }
145
+ }
146
+
147
+ &.highlight {
148
+ border-color: var(--e-c-mono-700);
149
+
150
+ &::before {
151
+ background-color: var(--e-c-mono-700);
152
+ width: a.rem(8);
153
+ height: a.rem(8);
154
+ }
155
+
156
+ &::after {
157
+ content: '';
158
+ position: absolute;
159
+ display: block;
160
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='9' fill='none'%3E%3Cpath fill='%236B6B6B' d='M6.79 7.985a1 1 0 0 1-1.58 0L.256 1.614A1 1 0 0 1 1.045 0h9.91a1 1 0 0 1 .79 1.614L6.789 7.985Z'/%3E%3C/svg%3E");
161
+ width: 12px;
162
+ height: 9px;
163
+ left: 2px;
164
+ top: -12px;
165
+ }
166
+
167
+ &:active,
168
+ &:hover {
169
+ border-color: var(--e-c-primary-01-500);
170
+
171
+ &::before {
172
+ background-color: var(--e-c-primary-01-500);
173
+ width: a.rem(8);
174
+ height: a.rem(8);
175
+ }
176
+
177
+ &::after {
178
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='9' fill='none'%3E%3Cpath fill='%234BA528' d='M6.79 7.985a1 1 0 0 1-1.58 0L.256 1.614A1 1 0 0 1 1.045 0h9.91a1 1 0 0 1 .79 1.614L6.789 7.985Z'/%3E%3C/svg%3E");
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ .slider__dot-wrapper {
185
+ position: absolute;
186
+ display: block;
187
+ top: -6px;
188
+ width: a.rem(16);
189
+ height: a.rem(16);
190
+ transform: translateX(-50%);
191
+ box-sizing: border-box;
192
+ }
193
+
194
+ .slider__tooltip-placeholder {
195
+ display: block;
196
+ width: 12px;
197
+ height: 12px;
198
+ }
199
+
200
+ .slider__thumb {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ position: relative;
205
+ height: var(--slider-thumb-height);
206
+ width: var(--slider-thumb-width);
207
+ background-color: rgba(var(--e-c-primary-01-200-rgb), 0.5);
208
+ border-radius: 100%;
209
+ top: calc(var(--slider-thumb-height) / -2 + (var(--slider-track-height) / 2));
210
+ transform: translateX(-50%);
211
+ transition: transform var(--e-trs-duration-fastest) var(--e-trs-easing-default);
212
+
213
+ &::before {
214
+ content: '';
215
+ position: absolute;
216
+ background-color: var(--e-c-primary-01-700);
217
+ inset: 6px;
218
+ border-radius: 100%;
219
+ }
220
+ }
221
+
222
+ .slider__thumb-value {
223
+ @include a.type(200, strong);
224
+
225
+ position: relative;
226
+ white-space: nowrap;
227
+ color: var(--e-c-mono-00);
228
+ user-select: none;
229
+ }
230
+
231
+ .slider__track {
232
+ top: calc(50% - var(--slider-track-height) / 2);
233
+ position: relative;
234
+ height: var(--slider-track-height);
235
+ }
236
+
237
+ .slider__lower-fill {
238
+ position: absolute;
239
+ top: 0;
240
+ left: 0;
241
+ height: 4px;
242
+ border-top-left-radius: var(--e-brd-radius-1);
243
+ border-bottom-left-radius: var(--e-brd-radius-1);
244
+ background-color: var(--e-c-primary-01-500);
245
+ }
246
+
247
+ .visually-hidden {
248
+ @include a.visually-hidden;
249
+ }
@@ -0,0 +1,174 @@
1
+ <script setup lang="ts">
2
+ import { watch, useId, useTemplateRef, onMounted, ref } from 'vue'
3
+ import { scaleValue } from '../../utils/math/scale-value'
4
+ import { clamp } from '../../utils/math/clamp'
5
+ import { UTooltip } from '../'
6
+
7
+ interface SliderDot {
8
+ value: number
9
+ isHighlighted?: boolean
10
+ }
11
+
12
+ export interface Slider {
13
+ name: string
14
+ dots?: SliderDot[]
15
+ label: string
16
+ min?: number
17
+ max?: number
18
+ step?: number
19
+ valuePrefix?: string
20
+ valueSuffix?: string
21
+ hideMinMax?: boolean
22
+ }
23
+
24
+ const {
25
+ dots = [],
26
+ min = 0,
27
+ max = 100,
28
+ step = 1,
29
+ valuePrefix = '',
30
+ valueSuffix = '',
31
+ } = defineProps<Slider>()
32
+
33
+ const model = defineModel<number>()
34
+ const emits = defineEmits(['change'])
35
+ const id = useId()
36
+ const isSliding = ref(false)
37
+ const isTouch = ref(false)
38
+ const thumbEl = useTemplateRef('thumb')
39
+ const lowerFillEl = useTemplateRef('lower-fill')
40
+ const controlEl = useTemplateRef('control')
41
+
42
+ const getValueString = (v) => `${valuePrefix}${v}${valueSuffix}`
43
+
44
+ const snapNumberToGrid = (n: number, grid: number, offset: number = 0) =>
45
+ Math.round(n / grid) * grid + offset
46
+
47
+ const getLeftPosition = (v) => scaleValue(Number(v), Number(min), Number(max), 0, 100, 2)
48
+ const setThumbPosition = () => {
49
+ const position = getLeftPosition(model.value)
50
+
51
+ thumbEl.value.style.left = `${position}%`
52
+ lowerFillEl.value.style.width = `${position}%`
53
+ }
54
+
55
+ onMounted(() => {
56
+ setThumbPosition()
57
+ })
58
+
59
+ watch(model, () => {
60
+ setThumbPosition()
61
+ emits('change', model.value)
62
+ })
63
+
64
+ const onPointerdown = (e: PointerEvent) => {
65
+ if (e.target.closest('.slider__dot')) {
66
+ return
67
+ }
68
+
69
+ e.preventDefault()
70
+ e.stopPropagation()
71
+
72
+ isSliding.value = true
73
+
74
+ const leftPos = clamp(
75
+ ((e.clientX - controlEl.value.getBoundingClientRect().left) / controlEl.value.offsetWidth) *
76
+ 100,
77
+ 0,
78
+ 100,
79
+ )
80
+
81
+ model.value = clamp(
82
+ snapNumberToGrid(scaleValue(leftPos, 0, 100, min, max, 2), step, min % step),
83
+ min,
84
+ max,
85
+ )
86
+
87
+ isTouch.value = e.pointerType !== 'mouse'
88
+
89
+ controlEl.value.addEventListener('pointermove', onPointermove)
90
+ document.addEventListener('pointerup', onPointerup)
91
+ }
92
+ const onPointermove = (e: PointerEvent) => {
93
+ e.preventDefault()
94
+ e.stopPropagation()
95
+
96
+ const leftPos = clamp(
97
+ ((e.clientX - controlEl.value.getBoundingClientRect().left) / controlEl.value.offsetWidth) *
98
+ 100,
99
+ 0,
100
+ 100,
101
+ )
102
+
103
+ model.value = clamp(
104
+ snapNumberToGrid(scaleValue(leftPos, 0, 100, min, max, 2), step, min % step),
105
+ min,
106
+ max,
107
+ )
108
+ }
109
+ const onPointerup = () => {
110
+ isSliding.value = false
111
+
112
+ controlEl.value.removeEventListener('pointermove', onPointermove)
113
+ document.removeEventListener('pointerup', onPointerup)
114
+ }
115
+ </script>
116
+
117
+ <template>
118
+ <div :class="['slider', { 'is-sliding': isSliding, 'is-touch': isTouch }]">
119
+ <label class="visually-hidden" :for="id">{{ label }}</label>
120
+
121
+ <div v-if="!hideMinMax" class="slider__min-value">
122
+ {{ getValueString(min) }}
123
+ </div>
124
+
125
+ <div class="slider__slider">
126
+ <div class="slider__input">
127
+ <input :id v-model="model" :name type="range" :min :max :step @change="setThumbPosition" />
128
+ </div>
129
+
130
+ <div ref="control" class="slider__control" @pointerdown.prevent="onPointerdown">
131
+ <div class="slider__track">
132
+ <div ref="lower-fill" class="slider__lower-fill"></div>
133
+
134
+ <div
135
+ v-for="(dot, idx) in dots"
136
+ :key="idx"
137
+ class="slider__dot-wrapper"
138
+ :style="{ left: `${getLeftPosition(dot.value)}%` }"
139
+ >
140
+ <UTooltip
141
+ :title="getValueString(dot.value)"
142
+ style="--tooltip-min-width: 0"
143
+ :disabled="isSliding"
144
+ :offset="10"
145
+ :offset-x="1"
146
+ >
147
+ <button
148
+ :class="[
149
+ 'slider__dot',
150
+ `${dot.value < model ? 'lower' : 'upper'}`,
151
+ { highlight: dot.isHighlighted },
152
+ ]"
153
+ type="button"
154
+ @click="model = dot.value"
155
+ >
156
+ <span class="visually-hidden">{{ getValueString(dot.value) }}</span>
157
+ </button>
158
+ </UTooltip>
159
+ </div>
160
+
161
+ <button ref="thumb" type="button" class="slider__thumb" tabindex="-1">
162
+ <span class="slider__thumb-value">{{ getValueString(model) }} </span>
163
+ </button>
164
+ </div>
165
+ </div>
166
+ </div>
167
+
168
+ <div v-if="!hideMinMax" class="slider__max-value">
169
+ {{ getValueString(max) }}
170
+ </div>
171
+ </div>
172
+ </template>
173
+
174
+ <style scoped lang="scss" src="./slider.scss"></style>
@@ -0,0 +1,25 @@
1
+ @use '../../base/abstracts' as a;
2
+
3
+ .slider-progress-animation {
4
+ &.lottie-ready {
5
+ .slider-progress-animation__lottie {
6
+ visibility: visible;
7
+ }
8
+ }
9
+ }
10
+
11
+ .slider-progress-animation__lottie {
12
+ position: relative;
13
+ visibility: hidden;
14
+ margin: 0 auto var(--e-space-6);
15
+ max-width: a.rem(460);
16
+ }
17
+
18
+ .slider-progress-animation__max-amount-image {
19
+ position: absolute;
20
+ top: 0;
21
+ right: 0;
22
+ aspect-ratio: 5/2;
23
+ width: 85px;
24
+ z-index: 1;
25
+ }
@@ -0,0 +1,65 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
3
+ import '@lottiefiles/lottie-player'
4
+ import { USlider } from '../'
5
+ import { Slider } from '../slider/u-slider.vue'
6
+ import { Image } from '../../elements/types'
7
+
8
+ interface Props {
9
+ lottieSrc: string
10
+ slider: Slider
11
+ maxAmountImage: Image
12
+ }
13
+
14
+ const { lottieSrc } = defineProps<Props>()
15
+ const model = defineModel<number>()
16
+
17
+ const lottieWrapperRef = useTemplateRef('lottie-wrapper')
18
+ let lottieEl
19
+ let lottie
20
+ const lottieReady = ref(false)
21
+
22
+ const onChange = (value) => {
23
+ lottie.goToAndStop(value, true)
24
+ }
25
+
26
+ onMounted(() => {
27
+ // lottie-player web-component will be ready in this callback.
28
+ const tmp = document.createElement('lottie-player')
29
+ tmp.setAttribute('src', lottieSrc)
30
+ tmp.setAttribute('speed', '2.5')
31
+ lottieEl = lottieWrapperRef.value.appendChild(tmp)
32
+
33
+ lottieEl.addEventListener('load', () => {
34
+ lottie = lottieEl.getLottie()
35
+
36
+ // Set initial state of animation
37
+ lottieReady.value = true
38
+ lottie.playSegments([0, model.value], true)
39
+ })
40
+ })
41
+
42
+ onUnmounted(() => {
43
+ if (lottieEl) {
44
+ lottieEl.remove()
45
+ }
46
+ })
47
+ </script>
48
+
49
+ <template>
50
+ <div :class="['slider-progress-animation', { 'lottie-ready': lottieReady }]">
51
+ <div ref="lottie-wrapper" class="slider-progress-animation__lottie">
52
+ <div
53
+ v-if="maxAmountImage && model === 100"
54
+ class="slider-progress-animation__max-amount-image"
55
+ >
56
+ <slot name="maxAmountImage">
57
+ <img :src="maxAmountImage.src" :alt="maxAmountImage.alt" />
58
+ </slot>
59
+ </div>
60
+ </div>
61
+ <USlider v-model="model" v-bind="slider" @change="onChange" />
62
+ </div>
63
+ </template>
64
+
65
+ <style scoped lang="scss" src="./slider-progress-animation.scss"></style>
@@ -0,0 +1,7 @@
1
+ .sprite-animation {
2
+ canvas {
3
+ display: block;
4
+ width: 100%;
5
+ height: 100%;
6
+ }
7
+ }
@@ -0,0 +1,169 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, useTemplateRef } from 'vue'
3
+ import { animateValue } from '../../utils/functions/animate.js'
4
+ import { clamp } from '../../utils/math/clamp'
5
+
6
+ export interface SpriteAnimation {
7
+ spritesheetPath: string
8
+ framePositions: [number, number][]
9
+ frameWidth: number
10
+ frameHeight: number
11
+ width?: number
12
+ height?: number
13
+ initialFrame?: number
14
+ duration?: number
15
+ fps?: number
16
+ easing?: (x: number) => number
17
+ }
18
+
19
+ const {
20
+ spritesheetPath,
21
+ framePositions,
22
+ frameWidth,
23
+ frameHeight,
24
+ width = undefined,
25
+ height = undefined,
26
+ initialFrame = 0,
27
+ duration = 300,
28
+ fps = undefined,
29
+ easing = (x) => x, // linear easing
30
+ } = defineProps<SpriteAnimation>()
31
+
32
+ const emits = defineEmits(['ready', 'pause'])
33
+
34
+ const canvasEl = useTemplateRef('canvas')
35
+ const canvasWidth = computed(() => width || frameWidth)
36
+ const canvasHeight = computed(() => height || frameHeight)
37
+ const devicePixelRatio = window.devicePixelRatio || 1
38
+ let currentFrame = initialFrame
39
+ let ctx: CanvasRenderingContext2D
40
+ let spritesheet: HTMLImageElement
41
+ let isAnimating = false
42
+ let animation
43
+
44
+ const loadImage = (path: string) =>
45
+ new Promise<HTMLImageElement>((resolve, reject) => {
46
+ const img = document.createElement('img')
47
+ img.addEventListener('load', () => {
48
+ resolve(img)
49
+ })
50
+
51
+ img.addEventListener('error', () => {
52
+ reject(img)
53
+ })
54
+
55
+ // Load image
56
+ img.src = path
57
+ })
58
+
59
+ const draw = () => {
60
+ // If context was not created just avoid drawing anything
61
+ if (!ctx) return
62
+
63
+ const [x, y] = framePositions[currentFrame]
64
+
65
+ ctx.save()
66
+ ctx.scale(devicePixelRatio, devicePixelRatio)
67
+ ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
68
+ ctx.drawImage(
69
+ spritesheet,
70
+ x,
71
+ y,
72
+ frameWidth,
73
+ frameHeight,
74
+ 0,
75
+ 0,
76
+ canvasWidth.value,
77
+ canvasHeight.value,
78
+ )
79
+ ctx.restore()
80
+ }
81
+
82
+ const durationFromFps = () => (1000 / fps) * framePositions.length
83
+
84
+ const durationFromFrames = (start, end) =>
85
+ (Math.abs(end - start) / framePositions.length) * duration
86
+
87
+ const animate = (start, end) => {
88
+ isAnimating = true
89
+
90
+ animation = animateValue({
91
+ duration: fps ? durationFromFps() : durationFromFrames(start, end),
92
+ start,
93
+ easing: fps ? undefined : easing,
94
+ end,
95
+ onUpdate(value) {
96
+ toFrame(value)
97
+ },
98
+ onComplete(value) {
99
+ isAnimating = false
100
+ emits('pause', getFrameFromProgress(value))
101
+ },
102
+ onAbort(value) {
103
+ emits('pause', getFrameFromProgress(value))
104
+ isAnimating = false
105
+ },
106
+ })
107
+ }
108
+
109
+ const play = (start = 0, end = framePositions.length - 1) => {
110
+ animate(start, end)
111
+ }
112
+
113
+ const playReverse = (start = framePositions.length - 1, end = 0) => {
114
+ if (isAnimating) {
115
+ animation.abort()
116
+ }
117
+
118
+ animate(start, end)
119
+ }
120
+
121
+ const pause = () => {
122
+ if (isAnimating && animation) {
123
+ animation.abort()
124
+ }
125
+ }
126
+
127
+ const getFrameFromProgress = (progress: number) =>
128
+ clamp(Math.trunc(progress), 0, framePositions.length - 1)
129
+
130
+ const toFrame = (frame: number) => {
131
+ currentFrame = getFrameFromProgress(frame)
132
+ draw()
133
+ }
134
+
135
+ onMounted(async () => {
136
+ ctx = canvasEl.value.getContext('2d')
137
+ spritesheet = await loadImage(spritesheetPath)
138
+
139
+ emits('ready')
140
+
141
+ // Show initial frame
142
+ draw()
143
+ })
144
+
145
+ defineExpose({
146
+ play,
147
+ playReverse,
148
+ pause,
149
+ toFrame,
150
+ })
151
+ </script>
152
+
153
+ <template>
154
+ <div
155
+ class="sprite-animation"
156
+ :style="{
157
+ width: `${width}px` || 'auto',
158
+ height: `${height}px` || 'auto',
159
+ }"
160
+ >
161
+ <canvas
162
+ ref="canvas"
163
+ :width="canvasWidth * devicePixelRatio"
164
+ :height="canvasHeight * devicePixelRatio"
165
+ ></canvas>
166
+ </div>
167
+ </template>
168
+
169
+ <style scoped lang="scss" src="./sprite-animation.scss"></style>
@@ -21,3 +21,7 @@
21
21
  .progress-bar-label {
22
22
  @include a.type(100);
23
23
  }
24
+
25
+ .cell-progress-bar__progress-bar {
26
+ max-width: a.rem(78);
27
+ }