@kiva/kv-components 3.107.0 → 3.107.2

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 (177) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/components/.storybook/main.js +85 -0
  3. package/dist/components/.storybook/package.json +3 -0
  4. package/dist/components/.storybook/preview.js +61 -0
  5. package/dist/components/.storybook/tailwind.css +5 -0
  6. package/dist/components/KvAccordionItem.vue +130 -0
  7. package/dist/components/KvActivityRow.vue +33 -0
  8. package/dist/components/KvBorrowerImage.vue +179 -0
  9. package/dist/components/KvButton.vue +287 -0
  10. package/dist/components/KvCarousel.vue +297 -0
  11. package/dist/components/KvCartModal.vue +365 -0
  12. package/dist/components/KvCheckbox.vue +203 -0
  13. package/dist/components/KvChip.vue +54 -0
  14. package/dist/components/KvClassicLoanCard.vue +527 -0
  15. package/dist/components/KvCommentsAdd.vue +135 -0
  16. package/dist/components/KvCommentsContainer.vue +84 -0
  17. package/dist/components/KvCommentsHeartButton.vue +70 -0
  18. package/dist/components/KvCommentsList.vue +68 -0
  19. package/dist/components/KvCommentsListItem.vue +241 -0
  20. package/dist/components/KvCommentsReplyButton.vue +52 -0
  21. package/dist/components/KvContentfulImg.vue +273 -0
  22. package/dist/components/KvCountdownTimer.vue +59 -0
  23. package/dist/components/KvExpandable.vue +84 -0
  24. package/dist/components/KvExpandableQuestion.vue +120 -0
  25. package/dist/components/KvFlag.vue +120 -0
  26. package/dist/components/KvGrid.vue +28 -0
  27. package/dist/components/KvImpactDashboardHeader.vue +40 -0
  28. package/dist/components/KvInlineActivityCard.vue +55 -0
  29. package/dist/components/KvInlineActivityFeed.vue +38 -0
  30. package/dist/components/KvIntroductionLoanCard.vue +446 -0
  31. package/dist/components/KvLendAmountButton.vue +65 -0
  32. package/dist/components/KvLendCta.vue +451 -0
  33. package/dist/components/KvLightbox.vue +334 -0
  34. package/dist/components/KvLineGraph.vue +128 -0
  35. package/dist/components/KvLoadingPlaceholder.vue +38 -0
  36. package/dist/components/KvLoadingSpinner.vue +81 -0
  37. package/dist/components/KvLoanActivities.vue +268 -0
  38. package/dist/components/KvLoanBookmark.vue +39 -0
  39. package/dist/components/KvLoanCallouts.vue +53 -0
  40. package/dist/components/KvLoanProgressGroup.vue +76 -0
  41. package/dist/components/KvLoanTag.vue +88 -0
  42. package/dist/components/KvLoanTeamPick.vue +44 -0
  43. package/dist/components/KvLoanUse.vue +92 -0
  44. package/dist/components/KvMap.vue +599 -0
  45. package/dist/components/KvMaterialIcon.vue +47 -0
  46. package/dist/components/KvPageContainer.vue +15 -0
  47. package/dist/components/KvPagination.vue +198 -0
  48. package/dist/components/KvPieChart.vue +257 -0
  49. package/dist/components/KvPopper.vue +178 -0
  50. package/dist/components/KvProgressBar.vue +149 -0
  51. package/dist/components/KvRadio.vue +198 -0
  52. package/dist/components/KvSelect.vue +114 -0
  53. package/dist/components/KvSideSheet.vue +134 -0
  54. package/dist/components/KvSwitch.vue +143 -0
  55. package/dist/components/KvTab.vue +90 -0
  56. package/dist/components/KvTabPanel.vue +64 -0
  57. package/dist/components/KvTabs.vue +182 -0
  58. package/dist/components/KvTextInput.vue +247 -0
  59. package/dist/components/KvTextLink.vue +138 -0
  60. package/dist/components/KvThemeProvider.vue +122 -0
  61. package/dist/components/KvToast.vue +221 -0
  62. package/dist/components/KvTooltip.vue +168 -0
  63. package/dist/components/KvTreeMapChart.vue +229 -0
  64. package/dist/components/KvUserAvatar.vue +132 -0
  65. package/dist/components/KvVerticalCarousel.vue +156 -0
  66. package/dist/components/KvVotingCard.vue +160 -0
  67. package/dist/components/KvVotingCardV2.vue +154 -0
  68. package/dist/components/KvWideLoanCard.vue +432 -0
  69. package/dist/components/stories/Forms.stories.js +62 -0
  70. package/dist/components/stories/KvAccordionItem.stories.js +24 -0
  71. package/dist/components/stories/KvActivityRow.stories.js +25 -0
  72. package/dist/components/stories/KvBorrowerImage.stories.js +68 -0
  73. package/dist/components/stories/KvButton.stories.js +144 -0
  74. package/dist/components/stories/KvCarousel.stories.js +426 -0
  75. package/dist/components/stories/KvCartModal.stories.js +54 -0
  76. package/dist/components/stories/KvCheckbox.stories.js +163 -0
  77. package/dist/components/stories/KvChip.stories.js +43 -0
  78. package/dist/components/stories/KvClassicLoanCard.stories.js +480 -0
  79. package/dist/components/stories/KvCommentsAdd.stories.js +32 -0
  80. package/dist/components/stories/KvCommentsContainer.stories.js +42 -0
  81. package/dist/components/stories/KvCommentsHeartButton.stories.js +25 -0
  82. package/dist/components/stories/KvCommentsList.stories.js +39 -0
  83. package/dist/components/stories/KvCommentsListItem.stories.js +45 -0
  84. package/dist/components/stories/KvCommentsReplyButton.stories.js +21 -0
  85. package/dist/components/stories/KvContentfulImg.stories.js +196 -0
  86. package/dist/components/stories/KvCountdownTimer.stories.js +30 -0
  87. package/dist/components/stories/KvExpandableQuestion.stories.js +129 -0
  88. package/dist/components/stories/KvFlag.stories.js +36 -0
  89. package/dist/components/stories/KvGrid.stories.js +97 -0
  90. package/dist/components/stories/KvImpactDashboardHeader.stories.js +22 -0
  91. package/dist/components/stories/KvInlineActivityCard.stories.js +69 -0
  92. package/dist/components/stories/KvInlineActivityFeed.stories.js +76 -0
  93. package/dist/components/stories/KvIntroductionLoanCard.stories.js +208 -0
  94. package/dist/components/stories/KvLendAmountButton.stories.js +31 -0
  95. package/dist/components/stories/KvLendCta.stories.js +177 -0
  96. package/dist/components/stories/KvLightbox.stories.js +304 -0
  97. package/dist/components/stories/KvLineGraph.stories.js +52 -0
  98. package/dist/components/stories/KvLoadingPlaceholder.stories.js +17 -0
  99. package/dist/components/stories/KvLoadingSpinner.stories.js +52 -0
  100. package/dist/components/stories/KvLoanActivities.stories.js +104 -0
  101. package/dist/components/stories/KvLoanBookmark.stories.js +22 -0
  102. package/dist/components/stories/KvLoanCallouts.stories.js +22 -0
  103. package/dist/components/stories/KvLoanProgressGroup.stories.js +29 -0
  104. package/dist/components/stories/KvLoanTag.stories.js +61 -0
  105. package/dist/components/stories/KvLoanTeamPick.stories.js +20 -0
  106. package/dist/components/stories/KvLoanUse.stories.js +60 -0
  107. package/dist/components/stories/KvMap.stories.js +121 -0
  108. package/dist/components/stories/KvMaterialIcon.stories.js +201 -0
  109. package/dist/components/stories/KvPageContainer.stories.js +50 -0
  110. package/dist/components/stories/KvPagination.stories.js +70 -0
  111. package/dist/components/stories/KvPieChart.stories.js +47 -0
  112. package/dist/components/stories/KvProgressBar.stories.js +53 -0
  113. package/dist/components/stories/KvRadio.stories.js +140 -0
  114. package/dist/components/stories/KvSelect.stories.js +125 -0
  115. package/dist/components/stories/KvSideSheet.stories.js +50 -0
  116. package/dist/components/stories/KvSwitch.stories.js +66 -0
  117. package/dist/components/stories/KvTabs.stories.js +106 -0
  118. package/dist/components/stories/KvTextInput.stories.js +194 -0
  119. package/dist/components/stories/KvTextLink.stories.js +55 -0
  120. package/dist/components/stories/KvThemeProvider.stories.js +178 -0
  121. package/dist/components/stories/KvToast.stories.js +117 -0
  122. package/dist/components/stories/KvTooltip.stories.js +26 -0
  123. package/dist/components/stories/KvTreeMapChart.stories.js +42 -0
  124. package/dist/components/stories/KvUserAvatar.stories.js +47 -0
  125. package/dist/components/stories/KvVerticalCarousel.stories.js +168 -0
  126. package/dist/components/stories/KvVotingCard.stories.js +33 -0
  127. package/dist/components/stories/KvVotingCardV2.stories.js +89 -0
  128. package/dist/components/stories/KvWideLoanCard.stories.js +292 -0
  129. package/dist/components/stories/StyleguidePrimitives.stories.js +499 -0
  130. package/dist/components/stories/StyleguideProse.stories.js +215 -0
  131. package/dist/data/countries-borders.json +1 -0
  132. package/dist/data/ne_110m_admin_0_countries.json +1 -0
  133. package/dist/utils/Alea.js +9 -0
  134. package/dist/utils/attrs.js +7 -0
  135. package/dist/utils/carousels.js +8 -0
  136. package/dist/{attrs.js → utils/chunk-3HK4G4NT.js} +1 -0
  137. package/dist/{loanCard.js → utils/chunk-55HF2ORX.js} +1 -0
  138. package/dist/{expander.js → utils/chunk-AY3PR5S4.js} +3 -2
  139. package/dist/{carousels.js → utils/chunk-AZPWOFD5.js} +1 -0
  140. package/dist/{printing.js → utils/chunk-B5J5WLAH.js} +1 -0
  141. package/dist/{Alea.js → utils/chunk-GPSH6OPA.js} +2 -1
  142. package/dist/{scrollLock.js → utils/chunk-HIY5IW65.js} +2 -1
  143. package/dist/{treemap.js → utils/chunk-MSMZIN54.js} +1 -0
  144. package/dist/{imageUtils.js → utils/chunk-OXJCCNNW.js} +1 -0
  145. package/dist/{touchEvents.js → utils/chunk-S3MABILA.js} +3 -2
  146. package/dist/{mapUtils.js → utils/chunk-VIGEMAKO.js} +5 -4
  147. package/dist/utils/chunk-YCNMJ4YV.js +37 -0
  148. package/dist/{loanUtils.js → utils/chunk-YFEC5ODJ.js} +7 -6
  149. package/dist/utils/expander.js +9 -0
  150. package/dist/utils/imageUtils.js +9 -0
  151. package/dist/utils/index.cjs +1118 -0
  152. package/dist/utils/index.js +166 -0
  153. package/dist/utils/loanCard.js +9 -0
  154. package/dist/utils/loanUtils.js +23 -0
  155. package/dist/utils/mapUtils.js +15 -0
  156. package/dist/utils/printing.js +9 -0
  157. package/dist/utils/scrollLock.js +13 -0
  158. package/dist/{throttle.js → utils/throttle.js} +1 -0
  159. package/dist/utils/touchEvents.js +11 -0
  160. package/dist/utils/treemap.js +7 -0
  161. package/package.json +11 -7
  162. package/utils/index.js +14 -0
  163. package/index.js +0 -3
  164. /package/dist/{Alea.cjs → utils/Alea.cjs} +0 -0
  165. /package/dist/{attrs.cjs → utils/attrs.cjs} +0 -0
  166. /package/dist/{carousels.cjs → utils/carousels.cjs} +0 -0
  167. /package/dist/{chunk-HV3AUBFT.js → utils/chunk-HV3AUBFT.js} +0 -0
  168. /package/dist/{expander.cjs → utils/expander.cjs} +0 -0
  169. /package/dist/{imageUtils.cjs → utils/imageUtils.cjs} +0 -0
  170. /package/dist/{loanCard.cjs → utils/loanCard.cjs} +0 -0
  171. /package/dist/{loanUtils.cjs → utils/loanUtils.cjs} +0 -0
  172. /package/dist/{mapUtils.cjs → utils/mapUtils.cjs} +0 -0
  173. /package/dist/{printing.cjs → utils/printing.cjs} +0 -0
  174. /package/dist/{scrollLock.cjs → utils/scrollLock.cjs} +0 -0
  175. /package/dist/{throttle.cjs → utils/throttle.cjs} +0 -0
  176. /package/dist/{touchEvents.cjs → utils/touchEvents.cjs} +0 -0
  177. /package/dist/{treemap.cjs → utils/treemap.cjs} +0 -0
@@ -0,0 +1,287 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ ref="buttonRef"
5
+ :to="to"
6
+ :type="computedType"
7
+ :disabled="isDisabled"
8
+ class="hover:tw-no-underline focus:tw-no-underline tw-inline-block"
9
+ :class="{
10
+ 'tw-opacity-low': state === 'disabled',
11
+ 'tw-pointer-events-none': state === 'loading' || isDisabled
12
+ }"
13
+ @click="onClick"
14
+ >
15
+ <!-- eslint-disable max-len -->
16
+ <span
17
+ ref="buttonInnerRef"
18
+ class="tw-inline-flex tw-w-full tw-justify-center tw-items-center tw-rounded
19
+ tw-min-h-6 tw-relative tw-overflow-hidden tw-border tw-font-medium tw-text-center"
20
+ :class="computedClass"
21
+ >
22
+ <!-- eslint-enable max-len -->
23
+ <template v-if="state === 'loading'">
24
+ <kv-loading-spinner
25
+ class="tw-absolute tw-w-full tw-text-center tw-z-0"
26
+ :color="loadingColor"
27
+ />
28
+ </template>
29
+ <span
30
+ class="tw-py-1 tw-px-3 tw-z-10"
31
+ :class="{ 'tw-invisible': state === 'loading' }"
32
+ >
33
+ <slot></slot>
34
+ </span>
35
+ </span>
36
+ </component>
37
+ </template>
38
+
39
+ <script>
40
+ import {
41
+ computed,
42
+ onMounted,
43
+ ref,
44
+ toRefs,
45
+ watch,
46
+ } from 'vue-demi';
47
+ import KvLoadingSpinner from './KvLoadingSpinner.vue';
48
+
49
+ export default {
50
+ components: {
51
+ KvLoadingSpinner,
52
+ },
53
+ props: {
54
+ /**
55
+ * Use if linking to a Vue route
56
+ */
57
+ to: {
58
+ type: [String, Object],
59
+ default: null,
60
+ },
61
+ /**
62
+ * Use if linking to an external link or old-stack page
63
+ */
64
+ href: {
65
+ type: String,
66
+ default: null,
67
+ },
68
+ /**
69
+ * The behavior of the button when used in an HTML form.
70
+ * `button (default), submit, reset`
71
+ */
72
+ type: {
73
+ type: String,
74
+ default: 'button',
75
+ validator(value) {
76
+ return ['button', 'submit', 'reset'].includes(value);
77
+ },
78
+ },
79
+ /**
80
+ * Appearance of the button
81
+ * `primary (default), secondary, danger, link, ghost`
82
+ */
83
+ variant: {
84
+ type: String,
85
+ default: 'primary',
86
+ validator(value) {
87
+ return ['primary', 'secondary', 'link', 'ghost', 'danger', 'caution'].includes(value);
88
+ },
89
+ },
90
+ /**
91
+ * State of the button
92
+ * `'' (default), active, disabled, loading`
93
+ */
94
+ state: {
95
+ type: String,
96
+ default: '',
97
+ validator(value) {
98
+ return ['', 'active', 'disabled', 'loading'].includes(value);
99
+ },
100
+ },
101
+ },
102
+ emits: [
103
+ 'click',
104
+ ],
105
+ setup(props, { emit }) {
106
+ const {
107
+ to,
108
+ href,
109
+ type,
110
+ variant,
111
+ state,
112
+ } = toRefs(props);
113
+
114
+ const loadingColor = computed(() => {
115
+ switch (variant.value) {
116
+ case 'secondary':
117
+ case 'caution':
118
+ return 'black';
119
+ case 'ghost':
120
+ return 'brand';
121
+ default:
122
+ return 'white';
123
+ }
124
+ });
125
+
126
+ const computedClass = computed(() => {
127
+ let classes = '';
128
+ switch (variant.value) {
129
+ case 'primary':
130
+ default:
131
+ classes = 'tw-text-primary-inverse';
132
+ if (state.value === 'active') {
133
+ classes = `${classes} tw-bg-action-highlight tw-border-action-highlight`;
134
+ } else {
135
+ // eslint-disable-next-line max-len
136
+ classes = `${classes} tw-bg-action hover:tw-bg-action-highlight tw-border-action hover:tw-border-action-highlight`;
137
+ }
138
+ break;
139
+ case 'secondary':
140
+ classes = 'tw-text-primary';
141
+ if (state.value === 'active') {
142
+ classes = `${classes} tw-bg-secondary tw-border-primary`;
143
+ } else {
144
+ // eslint-disable-next-line max-len
145
+ classes = `${classes} tw-bg-primary hover:tw-bg-secondary tw-border-tertiary hover:tw-border-primary`;
146
+ }
147
+ break;
148
+ case 'danger':
149
+ classes = 'tw-text-primary-inverse';
150
+ if (state.value === 'active') {
151
+ classes = `${classes} tw-bg-danger-highlight tw-border-danger-highlight`;
152
+ } else {
153
+ // eslint-disable-next-line max-len
154
+ classes = `${classes} tw-bg-danger hover:tw-bg-danger-highlight tw-border-danger hover:tw-border-danger-highlight`;
155
+ }
156
+ break;
157
+ case 'link':
158
+ classes = 'tw-bg-primary-inverse tw-text-primary-inverse';
159
+ if (state.value === 'active') {
160
+ classes = `${classes} tw-border-secondary`;
161
+ } else {
162
+ classes = `${classes} tw-border-primary hover:tw-border-secondary`;
163
+ }
164
+ break;
165
+ case 'ghost':
166
+ classes = 'tw-text-primary tw-border-transparent';
167
+ if (state.value === 'active') {
168
+ classes = `${classes} tw-bg-secondary`;
169
+ } else {
170
+ classes = `${classes} tw-bg-primary hover:tw-bg-secondary`;
171
+ }
172
+ break;
173
+ case 'caution':
174
+ classes = 'tw-text-primary tw-border-transparent';
175
+ if (state.value === 'active') {
176
+ classes = `${classes} tw-bg-caution-highlight`;
177
+ } else {
178
+ classes = `${classes} tw-bg-caution hover:tw-bg-caution-highlight`;
179
+ }
180
+ break;
181
+ }
182
+ return classes;
183
+ });
184
+
185
+ const isDisabled = computed(() => state.value === 'disabled' || state.value === 'loading');
186
+
187
+ const tag = computed(() => {
188
+ if (to.value) {
189
+ return 'router-link';
190
+ }
191
+ if (href.value) {
192
+ return 'a';
193
+ }
194
+ return 'button';
195
+ });
196
+
197
+ const computedType = computed(() => {
198
+ if (to.value || href.value) {
199
+ return null;
200
+ }
201
+ return type.value;
202
+ });
203
+
204
+ const buttonRef = ref(null);
205
+ const buttonInnerRef = ref(null);
206
+
207
+ const createRipple = (event) => {
208
+ // build an element to animate
209
+ const blipEl = document.createElement('span');
210
+ blipEl.classList = `
211
+ tw-absolute
212
+ tw-inline-block
213
+ tw-h-2
214
+ tw-w-2
215
+ tw-rounded-full
216
+ tw-transform
217
+ tw--translate-x-1/2
218
+ tw--translate-y-1/2
219
+ tw-opacity-0
220
+ tw-animate-ripple
221
+ motion-reduce:tw-animate-none
222
+ `;
223
+ blipEl.dataset.testid = 'ripple'; // for accessing in tests
224
+
225
+ // some variants shouldn't have a white blip
226
+ const darkBlipVariants = ['secondary', 'ghost'];
227
+ const blipBgColor = darkBlipVariants.includes(variant.value) ? 'tw-bg-tertiary' : 'tw-bg-primary';
228
+ blipEl.classList.add(blipBgColor);
229
+
230
+ // position the blip where the pointer click is or center it if keyboard
231
+ const fromClick = event.detail !== 0; // determine if click came from pointer or keyboard
232
+ const { clientX, clientY } = event;
233
+
234
+ if (buttonRef.value) {
235
+ const {
236
+ offsetLeft, offsetTop, offsetWidth, offsetHeight,
237
+ } = buttonRef.value;
238
+ let blipX;
239
+ let blipY;
240
+ if (fromClick) {
241
+ blipX = `${clientX - offsetLeft}px`;
242
+ blipY = `${clientY - offsetTop}px`;
243
+ } else {
244
+ blipX = `${offsetWidth / 2}px`;
245
+ blipY = `${offsetHeight / 2}px`;
246
+ }
247
+ blipEl.style.setProperty('left', blipX);
248
+ blipEl.style.setProperty('top', blipY);
249
+
250
+ // append the blip to the button, remove it when the animation is done
251
+ buttonInnerRef.value.appendChild(blipEl);
252
+ blipEl.addEventListener('animationend', function animationComplete() {
253
+ buttonInnerRef.value.removeChild(blipEl);
254
+ blipEl.removeEventListener('animationend', animationComplete);
255
+ });
256
+ }
257
+ };
258
+
259
+ const onClick = (event) => {
260
+ // Pass-through native click event to parent while adding ripple effect
261
+ emit('click', event);
262
+ createRipple(event);
263
+ };
264
+
265
+ const setHref = () => {
266
+ // if the component is a router-link, router-link will set the href
267
+ // if the href is passed as a prop, use that instead
268
+ if (href.value) {
269
+ buttonRef.value.href = href.value;
270
+ }
271
+ };
272
+ watch(href, () => setHref());
273
+ onMounted(() => setHref());
274
+
275
+ return {
276
+ buttonRef,
277
+ buttonInnerRef,
278
+ computedClass,
279
+ computedType,
280
+ isDisabled,
281
+ loadingColor,
282
+ onClick,
283
+ tag,
284
+ };
285
+ },
286
+ };
287
+ </script>
@@ -0,0 +1,297 @@
1
+ <template>
2
+ <section
3
+ ref="rootEl"
4
+ class="kv-carousel tw-overflow-hidden tw-w-full"
5
+ :class="{ 'lg:tw-relative': asideControls }"
6
+ aria-label="carousel"
7
+ >
8
+ <!-- Carousel Content -->
9
+ <div
10
+ class="tw-flex tw-gap-x-4"
11
+ :class="{
12
+ 'tw-mx-auto aside-controls-content': asideControls,
13
+ 'circle-carousel': inCircle
14
+ }"
15
+ @click.capture="onCarouselContainerClick"
16
+ >
17
+ <div
18
+ v-for="(slotName, index) in componentSlotKeys"
19
+ :key="index"
20
+ class="tw-flex-none tw-relative"
21
+ role="group"
22
+ :aria-label="`slide ${index + 1} of ${componentSlotKeys.length}`"
23
+ :aria-current="currentIndex === index ? 'true' : 'false'"
24
+ :aria-hidden="isAriaHidden(index)? 'true' : 'false'"
25
+ :tab-index="isAriaHidden(index) ? '-1' : false"
26
+ :class="{ 'tw-w-full': !multipleSlidesVisible || slideMaxWidth, 'circle-slide': inCircle }"
27
+ :style="slideMaxWidth ? `max-width:${slideMaxWidth}` :''"
28
+ >
29
+ <slot
30
+ :name="slotName"
31
+ ></slot>
32
+ </div>
33
+ </div>
34
+ <!-- Carousel Controls -->
35
+ <div
36
+ v-if="slideIndicatorCount > 1 && !isDotted"
37
+ class="kv-carousel__controls tw-flex
38
+ tw-justify-between md:tw-justify-center tw-items-center
39
+ tw-mt-4 tw-w-full"
40
+ :class="{ 'lg:tw-hidden': asideControls }"
41
+ >
42
+ <button
43
+ class="tw-text-primary
44
+ tw-rounded-full
45
+ tw-border-2 tw-border-primary
46
+ tw-h-4 tw-w-4
47
+ tw-flex tw-items-center tw-justify-center
48
+ disabled:tw-opacity-low disabled:tw-cursor-default"
49
+ :disabled="embla && !embla.canScrollPrev()"
50
+ @click="handleUserInteraction(previousIndex, 'click-left-arrow')"
51
+ >
52
+ <kv-material-icon
53
+ class="tw-w-4"
54
+ :icon="asideControls? mdiArrowLeft : mdiChevronLeft"
55
+ />
56
+ <span class="tw-sr-only">Show previous slide</span>
57
+ </button>
58
+ <div
59
+ :aria-label="`screen ${currentIndex + 1} of ${slideIndicatorCount}`"
60
+ class="tw-mx-2 md:tw-mx-3 lg:tw-mx-4 tw-invisible md:tw-visible"
61
+ >
62
+ {{ currentIndex + 1 }}/{{ slideIndicatorCount }}
63
+ </div>
64
+ <button
65
+ class="tw-text-primary
66
+ tw-rounded-full
67
+ tw-border-2 tw-border-primary
68
+ tw-h-4 tw-w-4
69
+ tw-flex tw-items-center tw-justify-center
70
+ disabled:tw-opacity-low disabled:tw-cursor-default"
71
+ :disabled="embla && !embla.canScrollNext()"
72
+ @click="handleUserInteraction(nextIndex, 'click-right-arrow')"
73
+ >
74
+ <kv-material-icon
75
+ class="tw-w-4"
76
+ :icon="asideControls ? mdiArrowRight : mdiChevronRight"
77
+ />
78
+ <span class="tw-sr-only">Show next slide</span>
79
+ </button>
80
+ </div>
81
+ <!-- Dotted Controls -->
82
+ <div
83
+ v-else-if="slideIndicatorCount > 1"
84
+ class="kv-carousel__controls tw-flex tw-justify-center tw-items-center tw-gap-1.5 tw-mt-4 tw-w-full"
85
+ >
86
+ <button
87
+ v-for="slide in slideIndicatorCount"
88
+ :key="slide"
89
+ @click="goToSlide(slide - 1)"
90
+ >
91
+ <div
92
+ class="tw-rounded-full tw-border tw-transition tw-duration-500 tw-ease-in-out"
93
+ :class="[
94
+ { 'tw-bg-black tw-border-black tw-h-1.5 tw-w-1.5': currentIndex === slide - 1 },
95
+ { 'tw-bg-tertiary tw-border-tertiary tw-h-1 tw-w-1': currentIndex !== slide - 1 }
96
+ ]"
97
+ >
98
+ </div>
99
+ </button>
100
+ </div>
101
+ <!-- Aside Buttons -->
102
+ <template v-if="asideControls">
103
+ <div
104
+ class="tw-hidden lg:tw-flex tw-absolute tw-h-full tw-top-0 tw-items-center"
105
+ style="background: linear-gradient(90deg, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%); width: 8%;"
106
+ >
107
+ <button
108
+ class="tw-text-primary tw-bg-gray-100
109
+ tw-rounded-full
110
+ tw-h-6 tw-w-6 tw-ml-3
111
+ tw-flex tw-items-center tw-justify-center
112
+ disabled:tw-opacity-low disabled:tw-cursor-default"
113
+ :disabled="embla && !embla.canScrollPrev()"
114
+ @click="handleUserInteraction(previousIndex, 'click-left-arrow')"
115
+ >
116
+ <kv-material-icon
117
+ class="tw-w-4"
118
+ :icon="mdiArrowLeft"
119
+ />
120
+ <span class="tw-sr-only">Show previous slide</span>
121
+ </button>
122
+ </div>
123
+ <div
124
+ class="tw-hidden lg:tw-flex tw-absolute tw-h-full
125
+ tw-top-0 tw-right-0 tw-items-center tw-justify-end tw-w-16"
126
+ style="background: linear-gradient(270deg, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%); width: 8%;"
127
+ >
128
+ <button
129
+ class="tw-text-primary tw-bg-gray-100
130
+ tw-rounded-full
131
+ tw-h-6 tw-w-6 tw-mr-3
132
+ tw-flex tw-items-center tw-justify-center
133
+ disabled:tw-opacity-low disabled:tw-cursor-default"
134
+ :disabled="embla && !embla.canScrollNext()"
135
+ @click="handleUserInteraction(nextIndex, 'click-right-arrow')"
136
+ >
137
+ <kv-material-icon
138
+ class="tw-w-4"
139
+ :icon="mdiArrowRight"
140
+ />
141
+ <span class="tw-sr-only">Show next slide</span>
142
+ </button>
143
+ </div>
144
+ </template>
145
+ </section>
146
+ </template>
147
+
148
+ <script>
149
+ import {
150
+ mdiChevronLeft,
151
+ mdiChevronRight,
152
+ mdiArrowLeft,
153
+ mdiArrowRight,
154
+ } from '@mdi/js';
155
+ import { carouselUtil } from '../utils/carousels';
156
+
157
+ import KvMaterialIcon from './KvMaterialIcon.vue';
158
+
159
+ export default {
160
+ components: {
161
+ KvMaterialIcon,
162
+ },
163
+ props: {
164
+ /**
165
+ * Should multiple slides be visible at a time.
166
+ * If true, a width must be set for each individual
167
+ * carousel slide or slideMaxWidth must
168
+ * be used.
169
+ * */
170
+ multipleSlidesVisible: {
171
+ type: Boolean,
172
+ default: false,
173
+ },
174
+ /**
175
+ * Options for the embla carousel - // https://davidcetinkaya.github.io/embla-carousel/api#options
176
+ * */
177
+ emblaOptions: {
178
+ type: Object,
179
+ default() {
180
+ return {};
181
+ },
182
+ },
183
+ /**
184
+ * The type of logic to implement when deciding how many slides
185
+ * to scroll when pressing the next/prev button
186
+ * `visible, auto`
187
+ * */
188
+ slidesToScroll: {
189
+ type: String,
190
+ default: 'auto',
191
+ validator: (value) => ['visible', 'auto'].indexOf(value) !== -1,
192
+ },
193
+ /**
194
+ * CSS value and unit to set the max width on responsive slides.
195
+ * Slide will be responsive full width until the max width value
196
+ * is reached - example value: '32.5rem'
197
+ * */
198
+ slideMaxWidth: {
199
+ type: String,
200
+ default: '',
201
+ },
202
+ /**
203
+ * Aside controls version of the carousel
204
+ * */
205
+ asideControls: {
206
+ type: Boolean,
207
+ default: false,
208
+ },
209
+ /**
210
+ * Dotted controls version of the carousel
211
+ * */
212
+ isDotted: {
213
+ type: Boolean,
214
+ default: false,
215
+ },
216
+ /**
217
+ * Enables carousel slides to have a circle effect
218
+ * */
219
+ inCircle: {
220
+ type: Boolean,
221
+ default: false,
222
+ },
223
+ },
224
+ emits: [
225
+ 'change',
226
+ 'interact-carousel',
227
+ ],
228
+ setup(props, { emit, slots }) {
229
+ const {
230
+ componentSlotKeys,
231
+ currentIndex,
232
+ embla,
233
+ goToSlide,
234
+ handleUserInteraction,
235
+ isAriaHidden,
236
+ nextIndex,
237
+ onCarouselContainerClick,
238
+ previousIndex,
239
+ reInit,
240
+ reInitVisible,
241
+ rootEl,
242
+ slideIndicatorCount,
243
+ slideIndicatorListLength,
244
+ slides,
245
+ } = carouselUtil(props, { emit, slots });
246
+
247
+ return {
248
+ componentSlotKeys,
249
+ currentIndex,
250
+ embla,
251
+ goToSlide,
252
+ handleUserInteraction,
253
+ isAriaHidden,
254
+ mdiArrowLeft,
255
+ mdiArrowRight,
256
+ mdiChevronLeft,
257
+ mdiChevronRight,
258
+ nextIndex,
259
+ onCarouselContainerClick,
260
+ previousIndex,
261
+ reInit,
262
+ reInitVisible,
263
+ rootEl,
264
+ slideIndicatorCount,
265
+ slideIndicatorListLength,
266
+ slides,
267
+ };
268
+ },
269
+ };
270
+ </script>
271
+
272
+ <style scoped>
273
+ .aside-controls-content {
274
+ @screen lg {
275
+ width: 82%;
276
+ }
277
+ }
278
+
279
+ .circle-slide {
280
+ width: auto;
281
+ }
282
+
283
+ .circle-slide.is-selected {
284
+ opacity: 1;
285
+ transform: scale(1.2);
286
+ max-width: 300px;
287
+ }
288
+
289
+ .circle-slide:not(.is-selected) {
290
+ opacity: 0.5;
291
+ transform: scale(0.7);
292
+ }
293
+
294
+ .circle-carousel {
295
+ margin: 0 auto;
296
+ }
297
+ </style>