@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,229 @@
1
+ <template>
2
+ <figure class="treemap-figure tw-relative">
3
+ <!-- treemap -->
4
+ <div
5
+ v-for="(block, index) in blocks"
6
+ :key="`block-${index}`"
7
+ class="tw-absolute"
8
+ :style="blockPositionStyles(block)"
9
+ >
10
+ <kv-loading-placeholder
11
+ v-if="loading"
12
+ style="width: calc(100% - 0.25rem); height: calc(100% - 0.25rem);"
13
+ />
14
+ <p
15
+ v-if="!loading"
16
+ class="tw-rounded-sm tw-box-border tw-overflow-hidden tw-text-small"
17
+ :class="colorClasses(index)"
18
+ style="width: calc(100% - 0.25rem); height: calc(100% - 0.25rem);"
19
+ @mouseenter="setHoverBlock(block)"
20
+ >
21
+ <span class="tw-block tw-px-1 tw-py-0.5 tw-truncate">
22
+ {{ block.data.label }} {{ block.data.percent }}
23
+ </span>
24
+ </p>
25
+ </div>
26
+ <!-- tooltip -->
27
+ <div
28
+ ref="tooltipController"
29
+ class="tw-absolute"
30
+ :style="blockPositionStyles(activeBlock)"
31
+ ></div>
32
+ <kv-tooltip
33
+ v-if="!loading && tooltipControllerElement"
34
+ :controller="tooltipControllerElement"
35
+ >
36
+ <template #title>
37
+ {{ activeBlock.data.label }}
38
+ </template>
39
+ {{ activeBlock.data.value }} ({{ activeBlock.data.percent }})
40
+ </kv-tooltip>
41
+ </figure>
42
+ </template>
43
+
44
+ <script>
45
+ import numeral from 'numeral';
46
+ import kvTokensPrimitives from '@kiva/kv-tokens/primitives.json';
47
+ import { getTreemap } from '../utils/treemap';
48
+ import { throttle } from '../utils/throttle';
49
+ import KvTooltip from './KvTooltip.vue';
50
+ import KvLoadingPlaceholder from './KvLoadingPlaceholder.vue';
51
+
52
+ const { breakpoints } = kvTokensPrimitives;
53
+
54
+ export default {
55
+ name: 'TreeMapFigure',
56
+ components: {
57
+ KvLoadingPlaceholder,
58
+ KvTooltip,
59
+ },
60
+ props: {
61
+ loading: {
62
+ type: Boolean,
63
+ default: true,
64
+ },
65
+ values: {
66
+ type: Array,
67
+ default: () => [],
68
+ },
69
+ },
70
+ data() {
71
+ return {
72
+ screenWidth: 0,
73
+ activeBlock: { data: {} },
74
+ tooltipControllerElement: null,
75
+ };
76
+ },
77
+ computed: {
78
+ activeBreakpoints() {
79
+ return Object.keys(breakpoints)
80
+ .filter((name) => this.screenWidth >= breakpoints[name]);
81
+ },
82
+ // Used to determine if blocks should be displayed in landscape or portrait orientation
83
+ aboveMedium() {
84
+ return this.activeBreakpoints.includes('md');
85
+ },
86
+ // Calculate the blocks of the treemap, but don't apply any formatting
87
+ rawBlocks() {
88
+ // Use static blocks when no data is available (either loading or no loans)
89
+ if (!this.values?.length) {
90
+ return this.loading ? [
91
+ {
92
+ x: 0, y: 0, width: 60, height: 40,
93
+ },
94
+ { x: 60, y: 0, height: 40 },
95
+ { x: 0, y: 40, width: 30 },
96
+ { x: 30, y: 40 },
97
+ ] : [
98
+ { x: 0, y: 0, data: { label: 'No loans yet' } },
99
+ ];
100
+ }
101
+
102
+ // Calculate treemap blocks using canvas size 100x100 to easily translate to percentages
103
+ const blocks = getTreemap({
104
+ data: this.values,
105
+ width: 100,
106
+ height: 100,
107
+ });
108
+
109
+ // Blocks smaller than 8% in either dimension will not display well,
110
+ // so combine them into one large 'Other' block
111
+ // If we only have 1 small block though, don't make the 'Other' block
112
+ const tooSmall = blocks.filter((block) => block.width <= 8 || block.height <= 8);
113
+ if (tooSmall.length === 1) return blocks;
114
+
115
+ const bigBlocks = blocks.filter((block) => block.width > 8 && block.height > 8);
116
+ return [...bigBlocks, this.reduceBlocks(tooSmall)];
117
+ },
118
+ // This is the array that is iterated over in the template
119
+ blocks() {
120
+ return this.rawBlocks.map((block) => {
121
+ const {
122
+ data, x, y, width, height,
123
+ } = block ?? {};
124
+ const { percent } = data ?? {};
125
+
126
+ return {
127
+ data: {
128
+ ...data,
129
+ percent: numeral(percent).format('0[.]0%'),
130
+ },
131
+ // flip x/y when going between small (portrait) and medium (landscape) screens
132
+ x: this.aboveMedium ? x : y,
133
+ y: this.aboveMedium ? y : x,
134
+ width: this.aboveMedium ? width : height,
135
+ height: this.aboveMedium ? height : width,
136
+ };
137
+ });
138
+ },
139
+ },
140
+ mounted() {
141
+ this.screenWidth = window.innerWidth;
142
+ window.addEventListener('resize', throttle(() => {
143
+ this.screenWidth = window.innerWidth;
144
+ }, 200));
145
+
146
+ // Used by KvTooltip/KvPopper to position the tooltip
147
+ if (this.$refs?.tooltipController) {
148
+ this.tooltipControllerElement = this.$refs.tooltipController;
149
+ }
150
+ },
151
+ methods: {
152
+ // Used by the p elements in the main v-for loop to determine background and text color
153
+ colorClasses(index) {
154
+ const classes = [];
155
+ const total = this.blocks?.length ?? 1;
156
+
157
+ // background
158
+ const size = (10 - Math.round((index / total) * 9)) * 100;
159
+ classes.push(size === 600 ? 'tw-bg-brand' : `tw-bg-brand-${size}`);
160
+
161
+ // text
162
+ classes.push(size > 700 ? 'tw-text-white' : 'tw-text-black');
163
+
164
+ return classes;
165
+ },
166
+ // Used by the divs in the main v-for for block positions and the tooltip controller div to match positions
167
+ // with the current active block
168
+ blockPositionStyles(block) {
169
+ return {
170
+ left: `${block.x}%`,
171
+ top: `${block.y}%`,
172
+ width: block.width ? `${block.width}%` : null,
173
+ height: block.height ? `${block.height}%` : null,
174
+ right: block.width ? null : '0',
175
+ bottom: block.height ? null : '0',
176
+ };
177
+ },
178
+ // Combine all small blocks into a single 'Other' block that will be at the bottom right of the figure.
179
+ reduceBlocks(blocks) {
180
+ return blocks?.reduce((other, block) => {
181
+ /* eslint-disable no-param-reassign */
182
+ // Use the smallest x for the overall x
183
+ if (block.x < other.x) {
184
+ other.x = block.x;
185
+ }
186
+ // Use the smallest y for the overall y
187
+ if (block.y < other.y) {
188
+ other.y = block.y;
189
+ }
190
+ // Sum up all values and percents
191
+ other.data.value += block.data.value;
192
+ other.data.percent += block.data.percent;
193
+ return other;
194
+ /* eslint-ensable no-param-reassign */
195
+ }, {
196
+ x: 100,
197
+ y: 100,
198
+ width: 0,
199
+ height: 0,
200
+ data: {
201
+ label: 'Other',
202
+ value: 0,
203
+ percent: 0,
204
+ },
205
+ }) ?? [];
206
+ },
207
+ // Sets the block that will be used to position the tooltip and change the info displayed in the tooltip
208
+ setHoverBlock(block) {
209
+ this.activeBlock = block;
210
+ },
211
+ },
212
+ };
213
+ </script>
214
+
215
+ <style lang="postcss" scoped>
216
+ .treemap-figure {
217
+ height: 30rem;
218
+
219
+ /* account for every block having a 0.25rem right margin */
220
+ width: calc(100% + 0.25rem);
221
+ margin-right: -0.25rem;
222
+ }
223
+
224
+ @screen md {
225
+ .treemap-figure {
226
+ height: 20rem;
227
+ }
228
+ }
229
+ </style>
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <div
3
+ class="data-hj-suppress tw-flex"
4
+ :class="{ 'tw-w-3': isSmall, 'tw-w-6': !isSmall }"
5
+ >
6
+ <!-- User is anonymous or data is missing -->
7
+ <div
8
+ v-if="isAnonymousUser"
9
+ class="
10
+ tw-rounded-full
11
+ tw-bg-brand
12
+ tw-inline-flex tw-align-center tw-justify-center
13
+ "
14
+ :class="{ 'tw-w-3 tw-h-3': isSmall, 'tw-w-6 tw-h-6': !isSmall }"
15
+ >
16
+ <!-- Kiva K logo -->
17
+ <!-- eslint-disable max-len -->
18
+ <svg
19
+ class="tw-h-full tw-text-brand"
20
+ :class="{ 'tw-w-3 tw-h-3': isSmall }"
21
+ width="25"
22
+ height="37"
23
+ viewBox="0 0 25 37"
24
+ fill="none"
25
+ xmlns="http://www.w3.org/2000/svg"
26
+ >
27
+ <path
28
+ d="M8.22861 0.875H0.857178V36.3125H8.22861V0.875Z"
29
+ fill="white"
30
+ />
31
+ <path
32
+ d="M10.1143 23.2751C21.9428 23.2751 24.6857 13.2126 24.6857 11.4626H23.6571C11.8286 11.4626 9.08569 21.5251 9.08569 23.2751H10.1143Z"
33
+ fill="white"
34
+ />
35
+ <path
36
+ d="M9.08569 24.2376C9.08569 26.0751 11.1428 36.3126 23.8285 36.3126H24.8571C24.8571 34.4751 22.8 24.2376 10.1143 24.2376H9.08569Z"
37
+ fill="white"
38
+ />
39
+ </svg>
40
+ <!-- eslint-enable max-len -->
41
+ </div>
42
+ <!-- User is not anonymous and has an image -->
43
+ <div
44
+ v-else-if="!isLegacyPlaceholderAvatar(imageFilename) && imageFilename"
45
+ >
46
+ <img
47
+ :src="lenderImageUrl"
48
+ alt="Image of lender"
49
+ class="tw-rounded-full tw-inline-block"
50
+ :class="{ 'tw-w-3 tw-h-3': isSmall, 'tw-w-6 tw-h-6': !isSmall }"
51
+ >
52
+ </div>
53
+ <!-- User is not anonymous and does not have an image -->
54
+ <div
55
+ v-else-if="isLegacyPlaceholderAvatar(imageFilename) || !imageFilename"
56
+ class="
57
+ tw-rounded-full
58
+ tw-inline-flex tw-align-center tw-justify-center
59
+ "
60
+ :class="avatarClass()"
61
+ >
62
+ <!-- First Letter of lender name -->
63
+ <span class="tw-self-center">
64
+ {{ lenderNameFirstLetter }}
65
+ </span>
66
+ </div>
67
+ </div>
68
+ </template>
69
+
70
+ <script>
71
+ import { computed, toRefs } from 'vue-demi';
72
+ import { isLegacyPlaceholderAvatar, randomizedUserAvatarClass } from '../utils/imageUtils';
73
+
74
+ export default {
75
+ name: 'KvUserAvatar',
76
+ props: {
77
+ /**
78
+ * The name of the lender
79
+ */
80
+ lenderName: {
81
+ type: String,
82
+ default: '',
83
+ },
84
+ /**
85
+ * The image of the lender
86
+ */
87
+ lenderImageUrl: {
88
+ type: String,
89
+ default: '',
90
+ },
91
+ /**
92
+ * The image of the lender
93
+ */
94
+ isSmall: {
95
+ type: Boolean,
96
+ default: false,
97
+ },
98
+ },
99
+ setup(props) {
100
+ const {
101
+ lenderName,
102
+ lenderImageUrl,
103
+ isSmall,
104
+ } = toRefs(props);
105
+
106
+ const isAnonymousUser = computed(() => {
107
+ return (lenderName.value === '' && lenderImageUrl.value === '') || lenderName.value === 'Anonymous';
108
+ });
109
+
110
+ const avatarClass = () => {
111
+ const smallClass = isSmall?.value ? 'tw-w-3 tw-h-3 tw-text-h4' : 'tw-w-6 tw-h-6 tw-text-h2';
112
+ return `${randomizedUserAvatarClass()} ${smallClass}`;
113
+ };
114
+
115
+ const imageFilename = computed(() => {
116
+ return lenderImageUrl?.value?.split('/').pop() ?? '';
117
+ });
118
+
119
+ const lenderNameFirstLetter = computed(() => {
120
+ return lenderName?.value?.substring(0, 1).toUpperCase();
121
+ });
122
+
123
+ return {
124
+ isAnonymousUser,
125
+ avatarClass,
126
+ imageFilename,
127
+ lenderNameFirstLetter,
128
+ isLegacyPlaceholderAvatar,
129
+ };
130
+ },
131
+ };
132
+ </script>
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <div>
3
+ <section
4
+ ref="rootEl"
5
+ aria-label="carousel"
6
+ class="kv-carousel tw-overflow-hidden tw-relative"
7
+ >
8
+ <!-- Carousel Content -->
9
+ <div
10
+ class="tw-flex tw-flex-col"
11
+ :style="`height: ${heightStyle}`"
12
+ @click.capture="onCarouselContainerClick"
13
+ >
14
+ <div
15
+ v-for="(slotName, index) in componentSlotKeys"
16
+ :key="index"
17
+ role="group"
18
+ :aria-label="`slide ${index + 1} of ${componentSlotKeys.length}`"
19
+ :aria-current="currentIndex === index ? 'true' : 'false'"
20
+ :aria-hidden="isAriaHidden(index)? 'true' : 'false'"
21
+ :tab-index="isAriaHidden(index) ? '-1' : false"
22
+ >
23
+ <slot
24
+ :name="slotName"
25
+ ></slot>
26
+ </div>
27
+ </div>
28
+ </section>
29
+ <!-- Carousel Controls -->
30
+ <div
31
+ class="kv-carousel__controls tw-flex
32
+ tw-justify-between md:tw-justify-center tw-items-center tw-gap-2
33
+ tw-mt-2 tw-w-full"
34
+ >
35
+ <button
36
+ class="tw-text-primary
37
+ tw-rounded-full
38
+ tw-border-2 tw-border-primary
39
+ tw-h-4 tw-w-4
40
+ tw-flex tw-items-center tw-justify-center
41
+ disabled:tw-opacity-low disabled:tw-cursor-default"
42
+ :disabled="embla && !embla.canScrollPrev()"
43
+ @click="handleUserInteraction(previousIndex, 'click-left-arrow')"
44
+ >
45
+ <kv-material-icon
46
+ class="tw-w-4"
47
+ :icon="mdiChevronUp"
48
+ />
49
+ <span class="tw-sr-only">Show previous slide</span>
50
+ </button>
51
+ <button
52
+ class="tw-text-primary
53
+ tw-rounded-full
54
+ tw-border-2 tw-border-primary
55
+ tw-h-4 tw-w-4
56
+ tw-flex tw-items-center tw-justify-center
57
+ disabled:tw-opacity-low disabled:tw-cursor-default"
58
+ :disabled="embla && !embla.canScrollNext()"
59
+ @click="handleUserInteraction(nextIndex, 'click-right-arrow')"
60
+ >
61
+ <kv-material-icon
62
+ class="tw-w-4"
63
+ :icon="mdiChevronDown"
64
+ />
65
+ <span class="tw-sr-only">Show next slide</span>
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </template>
70
+
71
+ <script>
72
+ import {
73
+ mdiChevronUp,
74
+ mdiChevronDown,
75
+ } from '@mdi/js';
76
+ import { carouselUtil } from '../utils/carousels';
77
+ import KvMaterialIcon from './KvMaterialIcon.vue';
78
+
79
+ export default {
80
+ components: {
81
+ KvMaterialIcon,
82
+ },
83
+ props: {
84
+ /**
85
+ * Height style declaration of the vertical carousel.
86
+ * */
87
+ heightStyle: {
88
+ type: String,
89
+ default: '400px;',
90
+ },
91
+ /**
92
+ * Options for the embla carousel - // https://davidcetinkaya.github.io/embla-carousel/api#options
93
+ * */
94
+ emblaOptions: {
95
+ type: Object,
96
+ default() {
97
+ return {};
98
+ },
99
+ },
100
+ /**
101
+ * The type of logic to implement when deciding how many slides
102
+ * to scroll when pressing the next/prev button
103
+ * `visible, auto`
104
+ * */
105
+ slidesToScroll: {
106
+ type: String,
107
+ default: 'auto',
108
+ validator: (value) => ['visible', 'auto'].indexOf(value) !== -1,
109
+ },
110
+ },
111
+ emits: [
112
+ 'change',
113
+ 'interact-carousel',
114
+ ],
115
+ setup(props, { emit, slots }) {
116
+ const {
117
+ componentSlotKeys,
118
+ currentIndex,
119
+ embla,
120
+ goToSlide,
121
+ handleUserInteraction,
122
+ isAriaHidden,
123
+ nextIndex,
124
+ onCarouselContainerClick,
125
+ previousIndex,
126
+ reInit,
127
+ reInitVisible,
128
+ rootEl,
129
+ slideIndicatorCount,
130
+ slides,
131
+ } = carouselUtil(props, { emit, slots }, { axis: 'y' });
132
+
133
+ return {
134
+ componentSlotKeys,
135
+ currentIndex,
136
+ embla,
137
+ goToSlide,
138
+ handleUserInteraction,
139
+ isAriaHidden,
140
+ mdiChevronDown,
141
+ mdiChevronUp,
142
+ nextIndex,
143
+ onCarouselContainerClick,
144
+ previousIndex,
145
+ reInit,
146
+ reInitVisible,
147
+ rootEl,
148
+ slideIndicatorCount,
149
+ slides,
150
+ };
151
+ },
152
+ };
153
+ </script>
154
+
155
+ <style scoped>
156
+ </style>
@@ -0,0 +1,160 @@
1
+ <template>
2
+ <div
3
+ class="
4
+ kv-voting-card
5
+ tw-bg-white
6
+ tw-rounded
7
+ tw-shadow
8
+ tw-p-1
9
+ tw-flex
10
+ tw-flex-col
11
+ tw-max-w-300"
12
+ >
13
+ <div class="tw-relative tw-flex tw-w-full tw-bg-black tw-rounded tw-mb-1">
14
+ <slot name="image"></slot>
15
+ <div
16
+ class="
17
+ tw-absolute
18
+ tw-bottom-1
19
+ tw-left-1
20
+ tw-text-primary
21
+ tw-bg-white
22
+ tw-rounded
23
+ tw-text-h4
24
+ tw-inline-flex
25
+ tw-items-center"
26
+ style="padding: 2px 6px; text-transform: capitalize;"
27
+ >
28
+ <span
29
+ class="tw-inline-flex tw-align-text-top"
30
+ aria-hidden="true"
31
+ role="img"
32
+ >
33
+ <svg
34
+ class="tw-h-2 tw-w-2.5"
35
+ viewBox="0 0 20 20"
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ >
38
+ <!-- eslint-disable max-len -->
39
+ <path
40
+ d="m3.12088 18.7441c12.86212 0 15.87912-14.52631 15.87912-16.99996h-1.1209c-12.85272 0-15.8791 14.51376-15.8791 16.99996z"
41
+ fill="currentColor"
42
+ />
43
+ <!-- eslint-enable max-len -->
44
+ </svg>
45
+ </span>
46
+ <div>
47
+ {{ borrowerName }}
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <div class="tw-flex tw-flex-grow">
52
+ <div class="tw-flex-grow">
53
+ <h3 class="tw-font-medium">
54
+ {{ category }}
55
+ </h3>
56
+ </div>
57
+ <div class="tw-flex-shrink">
58
+ <kv-material-icon
59
+ v-if="showInfoIcon"
60
+ :icon="infoIcon"
61
+ class="tw-h-3 tw-w-3"
62
+ @click="handleInfoClick"
63
+ />
64
+ </div>
65
+ </div>
66
+ <div class="tw-flex tw-items-center tw-w-full tw-mb-1">
67
+ <kv-progress-bar
68
+ v-if="showPercentage"
69
+ class="tw-flex-grow"
70
+ :aria-label="'Percent of votes for ' + category"
71
+ :value="percentage"
72
+ />
73
+ <div
74
+ v-if="showPercentage"
75
+ class="tw-ml-2 tw-font-medium"
76
+ >
77
+ {{ percentage }}%
78
+ </div>
79
+ </div>
80
+ <kv-button
81
+ v-if="showVoteButton"
82
+ class="tw-w-full"
83
+ @click="castVote"
84
+ >
85
+ Vote
86
+ </kv-button>
87
+ </div>
88
+ </template>
89
+
90
+ <script>
91
+ import { mdiInformation } from '@mdi/js';
92
+ import { computed } from 'vue-demi';
93
+ import KvProgressBar from './KvProgressBar.vue';
94
+ import KvButton from './KvButton.vue';
95
+ import KvMaterialIcon from './KvMaterialIcon.vue';
96
+
97
+ export default {
98
+ name: 'KvVotingCard',
99
+ components: {
100
+ KvProgressBar,
101
+ KvButton,
102
+ KvMaterialIcon,
103
+ },
104
+ props: {
105
+ borrowerName: {
106
+ type: String,
107
+ default: '',
108
+ },
109
+ category: {
110
+ type: String,
111
+ default: '',
112
+ },
113
+ images: {
114
+ type: Array,
115
+ default: () => [],
116
+ },
117
+ percentage: {
118
+ type: Number,
119
+ default: 0,
120
+ },
121
+ showVoteButton: {
122
+ type: Boolean,
123
+ default: true,
124
+ },
125
+ showPercentage: {
126
+ type: Boolean,
127
+ default: true,
128
+ },
129
+ showInfoIcon: {
130
+ type: Boolean,
131
+ default: false,
132
+ },
133
+ },
134
+ setup() {
135
+ const infoIcon = computed(() => mdiInformation);
136
+ return {
137
+ infoIcon,
138
+ };
139
+ },
140
+
141
+ methods: {
142
+ castVote() {
143
+ this.$emit('vote', {
144
+ category: this.category,
145
+ });
146
+ },
147
+ handleInfoClick() {
148
+ this.$emit('info-click', {
149
+ category: this.category,
150
+ });
151
+ },
152
+ },
153
+ };
154
+ </script>
155
+
156
+ <style scoped>
157
+ .kv-voting-card {
158
+ max-width: 300px;
159
+ }
160
+ </style>