@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 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
- "name": "@redseed/redseed-ui-vue3",
3
- "version": "8.5.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.1.0",
16
- "@vueuse/core": "^14.1.0",
17
- "lodash": "^4.17.21",
18
- "lottie-web": "^5.13.0",
19
- "vue": "^3.5.25"
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, 768)
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,4 @@
1
+ import SectionSlider from './SectionSlider.vue'
2
+ import SectionSliderItem from './SectionSliderItem.vue'
3
+
4
+ export { SectionSlider, SectionSliderItem }
@@ -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>
@@ -0,0 +1,5 @@
1
+ import Skeleton from './Skeleton.vue'
2
+
3
+ export {
4
+ Skeleton,
5
+ }