@redseed/redseed-ui-vue3 8.5.0 → 8.7.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/index.js +2 -0
- package/package.json +20 -20
- package/src/components/Layouts/SectionHeader.vue +1 -1
- package/src/components/SectionSlider/SectionSlider.vue +227 -0
- package/src/components/SectionSlider/SectionSliderItem.vue +46 -0
- package/src/components/SectionSlider/index.js +4 -0
- package/src/components/Skeleton/Skeleton.vue +86 -0
- package/src/components/Skeleton/index.js +5 -0
package/index.js
CHANGED
|
@@ -32,6 +32,8 @@ export * from './src/components/MetaInfo'
|
|
|
32
32
|
export * from './src/components/Modal'
|
|
33
33
|
export * from './src/components/Pagination'
|
|
34
34
|
export * from './src/components/Progress'
|
|
35
|
+
export * from './src/components/SectionSlider'
|
|
36
|
+
export * from './src/components/Skeleton'
|
|
35
37
|
export * from './src/components/Social'
|
|
36
38
|
export * from './src/components/Sorting'
|
|
37
39
|
export * from './src/components/Switcher'
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
2
|
+
"name": "@redseed/redseed-ui-vue3",
|
|
3
|
+
"version": "8.7.0",
|
|
4
|
+
"description": "RedSeed UI Vue 3 components",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": "https://github.com/redseedtraining/redseed-ui",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@heroicons/vue": "2.2.0",
|
|
15
|
+
"@vueuse/components": "14.2.0",
|
|
16
|
+
"@vueuse/core": "14.2.1",
|
|
17
|
+
"lodash": "4.17.23",
|
|
18
|
+
"lottie-web": "5.13.0",
|
|
19
|
+
"vue": "3.5.28"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -30,7 +30,7 @@ const props = defineProps({
|
|
|
30
30
|
|
|
31
31
|
const sectionHeaderElement = ref(null)
|
|
32
32
|
|
|
33
|
-
const { responsiveWidth } = useResponsiveWidth(sectionHeaderElement,
|
|
33
|
+
const { responsiveWidth } = useResponsiveWidth(sectionHeaderElement, 640)
|
|
34
34
|
|
|
35
35
|
const emit = defineEmits(['click:more-actions'])
|
|
36
36
|
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, useSlots } from 'vue'
|
|
3
|
+
import { useScroll, useEventListener } from '@vueuse/core'
|
|
4
|
+
import { Icon } from '../Icon'
|
|
5
|
+
import Section from '../Layouts/Section.vue'
|
|
6
|
+
import SectionHeader from '../Layouts/SectionHeader.vue'
|
|
7
|
+
import SectionSliderItem from './SectionSliderItem.vue'
|
|
8
|
+
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/24/outline'
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
items: {
|
|
12
|
+
type: Array,
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
featured: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
ai: {
|
|
20
|
+
type: Boolean,
|
|
21
|
+
default: false,
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const sectionVariant = computed(() => {
|
|
26
|
+
if (props.featured) return 'brand'
|
|
27
|
+
if (props.ai) return 'ai'
|
|
28
|
+
return 'primary'
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const sliderClasses = computed(() => [
|
|
32
|
+
'rsui-section-slider',
|
|
33
|
+
{
|
|
34
|
+
'rsui-section-slider--default': !props.featured && !props.ai,
|
|
35
|
+
'rsui-section-slider--featured': props.featured,
|
|
36
|
+
'rsui-section-slider--ai': props.ai,
|
|
37
|
+
},
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
const slots = useSlots()
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Unique id for the slider
|
|
44
|
+
*/
|
|
45
|
+
const sliderId = _.uniqueId('cs')
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate the item id for the item
|
|
49
|
+
*/
|
|
50
|
+
function generateItemId(index) {
|
|
51
|
+
return sliderId + ':' + index
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reference to the slider container element
|
|
56
|
+
*/
|
|
57
|
+
const sliderContainerRef = ref(null)
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Listener for keyboard events on the document
|
|
61
|
+
*/
|
|
62
|
+
useEventListener(document, 'keydown', (e) => {
|
|
63
|
+
if (e.key === 'ArrowRight') e.preventDefault()
|
|
64
|
+
if (e.key === 'ArrowLeft') e.preventDefault()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether the slider container is scrolling
|
|
69
|
+
*/
|
|
70
|
+
const { isScrolling } = useScroll(sliderContainerRef)
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Visible item ids
|
|
74
|
+
*/
|
|
75
|
+
const visibleItemIds = ref([])
|
|
76
|
+
|
|
77
|
+
function addVisibleItemId(id) {
|
|
78
|
+
visibleItemIds.value.push(id)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function removeVisibleItemId(id) {
|
|
82
|
+
visibleItemIds.value = visibleItemIds.value.filter(item => item !== id)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Maximum number of visible items
|
|
87
|
+
*/
|
|
88
|
+
const maxVisibleItems = computed(() => visibleItemIds.value.length)
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Slides - chunks items into groups based on visible count
|
|
92
|
+
*/
|
|
93
|
+
const slides = computed(() => {
|
|
94
|
+
const mappedItems = props.items.map((item, index) => {
|
|
95
|
+
return generateItemId(index + 1)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return _.chunk(mappedItems, maxVisibleItems.value)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Active slide index
|
|
103
|
+
*/
|
|
104
|
+
const activeSlideIndex = ref(0)
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether the previous button is disabled
|
|
108
|
+
*/
|
|
109
|
+
const disabledPrevButton = computed(() => isScrolling.value
|
|
110
|
+
|| maxVisibleItems.value === 0
|
|
111
|
+
|| props.items.length === maxVisibleItems.value
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether the next button is disabled
|
|
116
|
+
*/
|
|
117
|
+
const disabledNextButton = computed(() => isScrolling.value
|
|
118
|
+
|| maxVisibleItems.value === 0
|
|
119
|
+
|| props.items.length === maxVisibleItems.value
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Show the next slide
|
|
124
|
+
*/
|
|
125
|
+
function showNextSlide() {
|
|
126
|
+
if (disabledNextButton.value) return
|
|
127
|
+
|
|
128
|
+
if (activeSlideIndex.value === slides.value.length - 1) {
|
|
129
|
+
activeSlideIndex.value = 0
|
|
130
|
+
} else {
|
|
131
|
+
activeSlideIndex.value++
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const activeSlideFirstItemId = slides.value[activeSlideIndex.value][0]
|
|
135
|
+
const activeSlideFirstElement = document.getElementById(activeSlideFirstItemId)
|
|
136
|
+
|
|
137
|
+
activeSlideFirstElement.scrollIntoView({
|
|
138
|
+
behavior: 'smooth',
|
|
139
|
+
block: 'nearest',
|
|
140
|
+
inline: 'start',
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Show the previous slide
|
|
146
|
+
*/
|
|
147
|
+
function showPreviousSlide() {
|
|
148
|
+
if (disabledPrevButton.value) return
|
|
149
|
+
|
|
150
|
+
if (activeSlideIndex.value === 0) {
|
|
151
|
+
activeSlideIndex.value = slides.value.length - 1
|
|
152
|
+
} else {
|
|
153
|
+
activeSlideIndex.value--
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const activeSlideFirstItemId = slides.value[activeSlideIndex.value][0]
|
|
157
|
+
const activeSlideFirstElement = document.getElementById(activeSlideFirstItemId)
|
|
158
|
+
|
|
159
|
+
activeSlideFirstElement.scrollIntoView({
|
|
160
|
+
behavior: 'smooth',
|
|
161
|
+
block: 'nearest',
|
|
162
|
+
inline: 'start',
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
<template>
|
|
168
|
+
<Section :variant="sectionVariant"
|
|
169
|
+
:class="sliderClasses"
|
|
170
|
+
v-bind="$attrs"
|
|
171
|
+
>
|
|
172
|
+
<template #header>
|
|
173
|
+
<SectionHeader :showDivider="false" :showMoreActions="false">
|
|
174
|
+
<slot name="title"></slot>
|
|
175
|
+
|
|
176
|
+
<template v-if="slots.subtitle" #subtitle>
|
|
177
|
+
<slot name="subtitle"></slot>
|
|
178
|
+
</template>
|
|
179
|
+
|
|
180
|
+
<template v-if="slots['see-all']" #badge>
|
|
181
|
+
<slot name="see-all"></slot>
|
|
182
|
+
</template>
|
|
183
|
+
|
|
184
|
+
<template #actions>
|
|
185
|
+
<slot name="actions"
|
|
186
|
+
:showNextSlide="showNextSlide"
|
|
187
|
+
:showPreviousSlide="showPreviousSlide"
|
|
188
|
+
:disabledPrevButton="disabledPrevButton"
|
|
189
|
+
:disabledNextButton="disabledNextButton"
|
|
190
|
+
>
|
|
191
|
+
<button class="rsui-section-slider__action"
|
|
192
|
+
:disabled="disabledPrevButton"
|
|
193
|
+
@click="showPreviousSlide"
|
|
194
|
+
>
|
|
195
|
+
<Icon :invert="featured">
|
|
196
|
+
<ChevronLeftIcon />
|
|
197
|
+
</Icon>
|
|
198
|
+
</button>
|
|
199
|
+
|
|
200
|
+
<button class="rsui-section-slider__action"
|
|
201
|
+
:disabled="disabledNextButton"
|
|
202
|
+
@click="showNextSlide"
|
|
203
|
+
>
|
|
204
|
+
<Icon :invert="featured">
|
|
205
|
+
<ChevronRightIcon />
|
|
206
|
+
</Icon>
|
|
207
|
+
</button>
|
|
208
|
+
</slot>
|
|
209
|
+
</template>
|
|
210
|
+
</SectionHeader>
|
|
211
|
+
</template>
|
|
212
|
+
|
|
213
|
+
<div ref="sliderContainerRef"
|
|
214
|
+
class="rsui-section-slider__container"
|
|
215
|
+
>
|
|
216
|
+
<SectionSliderItem
|
|
217
|
+
v-for="(item, index) in items"
|
|
218
|
+
:key="index"
|
|
219
|
+
:id="generateItemId(index + 1)"
|
|
220
|
+
@visible="addVisibleItemId"
|
|
221
|
+
@hidden="removeVisibleItemId"
|
|
222
|
+
>
|
|
223
|
+
<slot name="item" :item="item"></slot>
|
|
224
|
+
</SectionSliderItem>
|
|
225
|
+
</div>
|
|
226
|
+
</Section>
|
|
227
|
+
</template>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
|
+
import { useEventListener, useElementVisibility } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
id: {
|
|
7
|
+
type: String,
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits(['visible', 'hidden'])
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reference to the item element
|
|
16
|
+
*/
|
|
17
|
+
const itemRef = ref(null)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether the item is visible
|
|
21
|
+
*/
|
|
22
|
+
const isVisible = useElementVisibility(itemRef, {
|
|
23
|
+
threshold: 1,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
watch(isVisible, (newVal) => {
|
|
27
|
+
if (newVal) emit('visible', props.id)
|
|
28
|
+
if (!newVal) emit('hidden', props.id)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Listener for keyboard events on the item
|
|
33
|
+
*/
|
|
34
|
+
useEventListener(itemRef, 'keydown', (e) => {
|
|
35
|
+
if (e.key === 'Tab') e.preventDefault()
|
|
36
|
+
})
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div :id="id"
|
|
41
|
+
ref="itemRef"
|
|
42
|
+
class="rsui-section-slider-item"
|
|
43
|
+
>
|
|
44
|
+
<slot></slot>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
line: {
|
|
6
|
+
type: Boolean,
|
|
7
|
+
default: false,
|
|
8
|
+
},
|
|
9
|
+
circle: {
|
|
10
|
+
type: Boolean,
|
|
11
|
+
default: false,
|
|
12
|
+
},
|
|
13
|
+
square: {
|
|
14
|
+
type: Boolean,
|
|
15
|
+
default: false,
|
|
16
|
+
},
|
|
17
|
+
full: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
half: {
|
|
22
|
+
type: Boolean,
|
|
23
|
+
default: false,
|
|
24
|
+
},
|
|
25
|
+
oneThird: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: false,
|
|
28
|
+
},
|
|
29
|
+
twoThirds: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
33
|
+
thick: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
rounded: {
|
|
38
|
+
type: Boolean,
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
start: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
end: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const shape = computed(() => {
|
|
52
|
+
if (props.circle) return 'circle'
|
|
53
|
+
if (props.square) return 'square'
|
|
54
|
+
return 'line'
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const widthClass = computed(() => {
|
|
58
|
+
if (props.twoThirds) return 'rsui-skeleton--two-thirds'
|
|
59
|
+
if (props.oneThird) return 'rsui-skeleton--one-third'
|
|
60
|
+
if (props.half) return 'rsui-skeleton--half'
|
|
61
|
+
if (props.full) return 'rsui-skeleton--full'
|
|
62
|
+
return 'rsui-skeleton--full'
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const positionClass = computed(() => {
|
|
66
|
+
if (props.end) return 'rsui-skeleton--end'
|
|
67
|
+
if (props.start) return 'rsui-skeleton--start'
|
|
68
|
+
return ''
|
|
69
|
+
})
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<template>
|
|
73
|
+
<div :class="['rsui-skeleton', widthClass, positionClass]">
|
|
74
|
+
<div
|
|
75
|
+
:class="[
|
|
76
|
+
shape === 'line' && 'rsui-skeleton__line',
|
|
77
|
+
shape === 'line' && props.thick && 'rsui-skeleton__line--thick',
|
|
78
|
+
shape === 'line' && props.rounded && 'rsui-skeleton__line--rounded',
|
|
79
|
+
shape === 'circle' && 'rsui-skeleton__circle',
|
|
80
|
+
shape === 'square' && 'rsui-skeleton__square',
|
|
81
|
+
shape === 'square' && props.rounded && 'rsui-skeleton__square--rounded',
|
|
82
|
+
]"
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
></div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|