@kiva/kv-components 3.48.0 → 3.48.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.
@@ -0,0 +1,429 @@
1
+ <template>
2
+ <div
3
+ class="
4
+ tw-flex
5
+ tw-flex-row
6
+ tw-flex-wrap
7
+ tw-bg-white
8
+ tw-rounded
9
+ tw-w-full
10
+ tw-pb-1
11
+ tw-p-1
12
+ tw-gap-1
13
+ tw-items-center
14
+ "
15
+ :class="{'tw-pointer-events-none' : isLoading }"
16
+ style="box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);"
17
+ >
18
+ <div
19
+ class="loan-card-active-hover tw-flex-1 tw-min-w-[275px] md:tw-min-w-[320px] md:tw-max-w-[320px]"
20
+ >
21
+ <!-- Borrower image -->
22
+ <kv-loading-placeholder
23
+ v-if="isLoading"
24
+ class="tw-mb-1 tw-w-full tw-rounded"
25
+ :style="{ height: '15rem' }"
26
+ />
27
+ <div
28
+ v-else
29
+ class="tw-relative tw-w-full"
30
+ @click="showLoanDetails"
31
+ >
32
+ <component
33
+ :is="tag"
34
+ :to="readMorePath"
35
+ :href="readMorePath"
36
+ class="tw-flex"
37
+ aria-label="Borrower image"
38
+ @click="clickReadMore('Photo')"
39
+ >
40
+ <kv-borrower-image
41
+ class="
42
+ tw-relative
43
+ tw-w-full
44
+ tw-bg-black
45
+ tw-rounded
46
+ "
47
+ :alt="`Photo of ${borrowerName}`"
48
+ :aspect-ratio="imageAspectRatio"
49
+ :default-image="{ width: imageDefaultWidth }"
50
+ :hash="imageHash"
51
+ :images="imageSizes"
52
+ :photo-path="photoPath"
53
+ />
54
+
55
+ <div v-if="countryName">
56
+ <p
57
+ class="
58
+ tw-absolute
59
+ tw-bottom-1
60
+ tw-left-1
61
+ tw-text-primary
62
+ tw-bg-white
63
+ tw-rounded
64
+ tw-p-1
65
+ tw-mb-0
66
+ tw-mr-2
67
+ tw-text-h4
68
+ tw-inline-flex
69
+ tw-items-center"
70
+ style="padding: 2px 6px; text-transform: capitalize;"
71
+ >
72
+ <kv-material-icon
73
+ class="tw-h-2 tw-w-2"
74
+ :icon="mdiMapMarker"
75
+ />
76
+ {{ formattedLocation }}
77
+ </p>
78
+ </div>
79
+ </component>
80
+ <kv-loan-bookmark
81
+ v-if="!isVisitor"
82
+ :loan-id="loanId"
83
+ :is-bookmarked="isBookmarked"
84
+ class="tw-absolute tw-right-1"
85
+ style="top: -6px;"
86
+ data-testid="loan-card-bookmark"
87
+ @toggle-bookmark="$emit('toggle-bookmark')"
88
+ />
89
+ </div>
90
+ </div>
91
+
92
+ <div
93
+ class="tw-flex tw-flex-col tw-flex-1 tw-px-1"
94
+ >
95
+ <h3 class="tw-hidden md:tw-inline-block">
96
+ {{ borrowerName }}
97
+ </h3>
98
+ <!-- Loan tag -->
99
+ <component
100
+ :is="tag"
101
+ :to="readMorePath"
102
+ :href="readMorePath"
103
+ class="tw-flex hover:tw-no-underline focus:tw-no-underline"
104
+ aria-label="Loan tag"
105
+ @click="clickReadMore('Tag')"
106
+ >
107
+ <kv-loan-tag
108
+ v-if="showTags && !isLoading"
109
+ :loan="loan"
110
+ :kv-track-function="kvTrackFunction"
111
+ />
112
+ </component>
113
+
114
+ <component
115
+ :is="tag"
116
+ :to="readMorePath"
117
+ :href="readMorePath"
118
+ class="loan-card-use tw-text-primary"
119
+ aria-label="Loan use"
120
+ @click="clickReadMore('Use')"
121
+ >
122
+ <!-- Loan use -->
123
+ <div class="tw-pt-1">
124
+ <div
125
+ v-if="isLoading"
126
+ class="tw-w-full"
127
+ style="height: 5.5rem;"
128
+ >
129
+ <div
130
+ v-for="(_n, i) in [...Array(4)]"
131
+ :key="i"
132
+ class="tw-h-2 tw-mb-1"
133
+ >
134
+ <kv-loading-placeholder />
135
+ </div>
136
+ </div>
137
+ <div v-else>
138
+ <kv-loan-use
139
+ :use="loanUse"
140
+ :loan-amount="loanAmount"
141
+ :status="loanStatus"
142
+ :borrower-count="loanBorrowerCount"
143
+ :name="borrowerName"
144
+ :distribution-model="distributionModel"
145
+ />
146
+ </div>
147
+ </div>
148
+ </component>
149
+ <!-- Loan call outs -->
150
+ <kv-loading-placeholder
151
+ v-if="isLoading || typeof loanCallouts === 'undefined'"
152
+ class="tw-mt-1.5 tw-mb-1"
153
+ :style="{ width: '60%', height: '1.75rem', 'border-radius': '500rem' }"
154
+ />
155
+
156
+ <kv-loan-callouts
157
+ v-else
158
+ :callouts="loanCallouts"
159
+ class="loan-callouts tw-my-1.5"
160
+ />
161
+
162
+ <div class="tw-flex tw-flex-row tw-justify-between tw-gap-1 md:tw-gap-3 ">
163
+ <!-- Fundraising -->
164
+ <div
165
+ v-if="!hasProgressData"
166
+ class="tw-w-full tw-pt-1 tw-pr-1"
167
+ >
168
+ <kv-loading-placeholder
169
+ class="tw-mb-0.5"
170
+ :style="{ width: '70%', height: '1.3rem' }"
171
+ />
172
+
173
+ <kv-loading-placeholder
174
+ class="tw-rounded"
175
+ :style="{ width: '70%', height: '0.5rem' }"
176
+ />
177
+ </div>
178
+ <component
179
+ :is="tag"
180
+ v-if="unreservedAmount > 0"
181
+ :to="readMorePath"
182
+ :href="readMorePath"
183
+ class="loan-card-progress tw-mt-1 tw-flex-grow"
184
+ aria-label="Loan progress"
185
+ @click="clickReadMore('Progress')"
186
+ >
187
+ <kv-loan-progress-group
188
+ id="loanProgress"
189
+ :money-left="`${unreservedAmount}`"
190
+ :progress-percent="fundraisingPercent"
191
+ class="tw-text-black"
192
+ />
193
+ </component>
194
+
195
+ <!-- CTA Button -->
196
+ <kv-loading-placeholder
197
+ v-if="!allDataLoaded"
198
+ class="tw-rounded tw-self-start"
199
+ :style="{ width: '9rem', height: '3rem' }"
200
+ />
201
+
202
+ <kv-lend-cta
203
+ v-else
204
+ :loan="loan"
205
+ :basket-items="basketItems"
206
+ :is-loading="isLoading"
207
+ :is-adding="isAdding"
208
+ :enable-five-dollars-notes="enableFiveDollarsNotes"
209
+ :five-dollars-selected="fiveDollarsSelected"
210
+ :kv-track-function="kvTrackFunction"
211
+ :show-view-loan="showViewLoan"
212
+ :custom-loan-details="customLoanDetails"
213
+ :external-links="externalLinks"
214
+ :route="route"
215
+ :user-balance="userBalance"
216
+ :get-cookie="getCookie"
217
+ :set-cookie="setCookie"
218
+ class="tw-mt-auto tw-self-end"
219
+ :class="{'tw-flex-grow' : unreservedAmount === 0, 'tw-flex-shrink-0' : unreservedAmount > 0}"
220
+ @add-to-basket="$emit('add-to-basket', $event)"
221
+ @show-loan-details="clickReadMore('ViewLoan')"
222
+ />
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </template>
227
+
228
+ <script>
229
+ import { loanCardComputedProperties, loanCardMethods } from '../utils/loanCard';
230
+ import KvLoanUse from './KvLoanUse.vue';
231
+ import KvBorrowerImage from './KvBorrowerImage.vue';
232
+ import KvLoanProgressGroup from './KvLoanProgressGroup.vue';
233
+ import KvLoanCallouts from './KvLoanCallouts.vue';
234
+ import KvLendCta from './KvLendCta.vue';
235
+ import KvLoanBookmark from './KvLoanBookmark.vue';
236
+ import KvLoanTag from './KvLoanTag.vue';
237
+ import KvMaterialIcon from './KvMaterialIcon.vue';
238
+ import KvLoadingPlaceholder from './KvLoadingPlaceholder.vue';
239
+
240
+ export default {
241
+ name: 'KvWideLoanCard',
242
+ components: {
243
+ KvBorrowerImage,
244
+ KvLoadingPlaceholder,
245
+ KvLoanUse,
246
+ KvLoanProgressGroup,
247
+ KvMaterialIcon,
248
+ KvLendCta,
249
+ KvLoanTag,
250
+ KvLoanCallouts,
251
+ KvLoanBookmark,
252
+ },
253
+ props: {
254
+ loanId: {
255
+ type: Number,
256
+ default: undefined,
257
+ },
258
+ loan: {
259
+ type: Object,
260
+ default: null,
261
+ },
262
+ customLoanDetails: {
263
+ type: Boolean,
264
+ default: false,
265
+ },
266
+ showTags: {
267
+ type: Boolean,
268
+ default: false,
269
+ },
270
+ categoryPageName: {
271
+ type: String,
272
+ default: '',
273
+ },
274
+ enableFiveDollarsNotes: {
275
+ type: Boolean,
276
+ default: false,
277
+ },
278
+ isAdding: {
279
+ type: Boolean,
280
+ default: false,
281
+ },
282
+ isVisitor: {
283
+ type: Boolean,
284
+ default: true,
285
+ },
286
+ basketItems: {
287
+ type: Array,
288
+ default: () => ([]),
289
+ },
290
+ isBookmarked: {
291
+ type: Boolean,
292
+ default: false,
293
+ },
294
+ kvTrackFunction: {
295
+ type: Function,
296
+ required: true,
297
+ },
298
+ photoPath: {
299
+ type: String,
300
+ required: true,
301
+ },
302
+ showViewLoan: {
303
+ type: Boolean,
304
+ default: false,
305
+ },
306
+ externalLinks: {
307
+ type: Boolean,
308
+ default: false,
309
+ },
310
+ route: {
311
+ type: Object,
312
+ default: undefined,
313
+ },
314
+ userBalance: {
315
+ type: String,
316
+ default: undefined,
317
+ },
318
+ getCookie: {
319
+ type: Function,
320
+ default: undefined,
321
+ },
322
+ setCookie: {
323
+ type: Function,
324
+ default: undefined,
325
+ },
326
+ fiveDollarsSelected: {
327
+ type: Boolean,
328
+ default: false,
329
+ },
330
+ customCallouts: {
331
+ type: Array,
332
+ default: () => ([]),
333
+ },
334
+ },
335
+ setup(props) {
336
+ const {
337
+ allDataLoaded,
338
+ borrowerName,
339
+ city,
340
+ countryName,
341
+ distributionModel,
342
+ formattedLocation,
343
+ fundraisingPercent,
344
+ hasProgressData,
345
+ imageHash,
346
+ isLoading,
347
+ loanAmount,
348
+ loanBorrowerCount,
349
+ loanCallouts,
350
+ loanStatus,
351
+ loanUse,
352
+ mdiMapMarker,
353
+ readMorePath,
354
+ state,
355
+ tag,
356
+ unreservedAmount,
357
+ } = loanCardComputedProperties(props);
358
+
359
+ const {
360
+ clickReadMore,
361
+ showLoanDetails,
362
+ } = loanCardMethods(props);
363
+
364
+ return {
365
+ allDataLoaded,
366
+ borrowerName,
367
+ city,
368
+ countryName,
369
+ distributionModel,
370
+ formattedLocation,
371
+ fundraisingPercent,
372
+ hasProgressData,
373
+ imageHash,
374
+ isLoading,
375
+ loanAmount,
376
+ loanBorrowerCount,
377
+ loanCallouts,
378
+ loanStatus,
379
+ loanUse,
380
+ mdiMapMarker,
381
+ readMorePath,
382
+ state,
383
+ tag,
384
+ unreservedAmount,
385
+ clickReadMore,
386
+ showLoanDetails,
387
+ };
388
+ },
389
+ computed: {
390
+ cardWidth() {
391
+ return '374px';
392
+ },
393
+ imageAspectRatio() {
394
+ return 1;
395
+ },
396
+ imageDefaultWidth() {
397
+ return 320;
398
+ },
399
+ imageSizes() {
400
+ return [
401
+ { width: this.imageDefaultWidth, viewSize: 1024 },
402
+ { width: this.imageDefaultWidth, viewSize: 768 },
403
+ { width: 416, viewSize: 480 },
404
+ { width: 374, viewSize: 414 },
405
+ { width: 335, viewSize: 375 },
406
+ ];
407
+ },
408
+ },
409
+ };
410
+ </script>
411
+
412
+ <style lang="postcss" scoped>
413
+ /** Shared with KvClassicLoanCard */
414
+ .loan-card-use:hover,
415
+ .loan-card-use:focus {
416
+ @apply tw-text-primary;
417
+ }
418
+ .loan-card-active-hover:hover .loan-card-use {
419
+ @apply tw-underline;
420
+ }
421
+ .loan-card-progress:hover,
422
+ .loan-card-progress:focus {
423
+ @apply tw-no-underline;
424
+ }
425
+ /** Unique to this loan card */
426
+ .loan-callouts >>> div{
427
+ @apply tw-flex-wrap tw-h-auto;
428
+ }
429
+ </style>
@@ -60,3 +60,9 @@ const props = {
60
60
  };
61
61
 
62
62
  export const Default = story(props);
63
+
64
+ export const Square = story({
65
+ ...props,
66
+ aspectRatio: 1,
67
+ defaultImage: { width: 300 },
68
+ });
@@ -28,6 +28,7 @@ const story = (args) => {
28
28
  :kv-track-function="kvTrackFunction"
29
29
  :photo-path="photoPath"
30
30
  :show-view-loan="showViewLoan"
31
+ :custom-callouts="customCallouts"
31
32
  />
32
33
  </div>
33
34
  `,
@@ -118,6 +119,38 @@ export const ShowTags = story({
118
119
  showTags: true,
119
120
  });
120
121
 
122
+ export const CustomCallouts = story({
123
+ loanId: loan.id,
124
+ loan: {
125
+ ...loan,
126
+ loanFundraisingInfo: {
127
+ fundedAmount: '950.00',
128
+ isExpiringSoon: false,
129
+ reservedAmount: '0.00',
130
+ },
131
+ },
132
+ kvTrackFunction,
133
+ photoPath,
134
+ showTags: true,
135
+ customCallouts: ['Loan Length: 15mo'],
136
+ });
137
+
138
+ export const CustomCalloutsWrap = story({
139
+ loanId: loan.id,
140
+ loan: {
141
+ ...loan,
142
+ loanFundraisingInfo: {
143
+ fundedAmount: '950.00',
144
+ isExpiringSoon: false,
145
+ reservedAmount: '0.00',
146
+ },
147
+ },
148
+ kvTrackFunction,
149
+ photoPath,
150
+ showTags: true,
151
+ customCallouts: ['Loan Length: 15mo', 'Long Dairy Processing'],
152
+ });
153
+
121
154
  export const Matched = story({
122
155
  loanId: loan.id,
123
156
  loan: {
@@ -2,6 +2,7 @@ import { mdiLightningBolt } from '@mdi/js';
2
2
  import KvLightbox from '../KvLightbox.vue';
3
3
  import KvMaterialIcon from '../KvMaterialIcon.vue';
4
4
  import KvButton from '../KvButton.vue';
5
+ import KvToast from '../KvToast.vue';
5
6
 
6
7
  export default {
7
8
  title: 'KvLightbox',
@@ -253,3 +254,51 @@ export const Informational = InformationalTemplate.bind({});
253
254
  Informational.args = {
254
255
  title: 'Lightbox Title',
255
256
  };
257
+
258
+ const ToastInLightboxTemplate = (args, {
259
+ argTypes,
260
+ }) => ({
261
+ props: Object.keys(argTypes),
262
+ components: {
263
+ KvToast,
264
+ KvButton,
265
+ KvLightbox,
266
+ },
267
+ template: `
268
+ <div>
269
+ <kv-button @click="isLightboxVisible = true;">Show lightbox</kv-button>
270
+
271
+ <kv-lightbox
272
+ :visible="isLightboxVisible"
273
+ title="Toast in Lightbox"
274
+ @lightbox-closed="isLightboxVisible = false"
275
+ >
276
+ <p>Click the button below to show a toast.</p>
277
+ <template #controls>
278
+ <kv-button @click="showToast(message, type, persist)">Show Toast</kv-button>
279
+ </template>
280
+ </kv-lightbox>
281
+
282
+ <!-- div below is a kludge for storybook docs -->
283
+ <div class="tw-fixed tw-z-toast tw-inset-0 tw-pointer-events-none">
284
+ <div class="tw-fixed tw-left-0 tw-right-0 tw-top-2 tw-pointer-events-auto">
285
+ <kv-toast ref="toastRef" @close="onClose" />
286
+ </div>
287
+ </div>
288
+ </div>
289
+ `,
290
+ data() {
291
+ return {
292
+ isLightboxVisible: false,
293
+ };
294
+ },
295
+ methods: {
296
+ showToast(messageInput, type, persistInput) {
297
+ this.$refs.toastRef.show(messageInput, type, persistInput);
298
+ },
299
+ onClose() {
300
+ },
301
+ },
302
+ });
303
+ export const toastInLightbox = ToastInLightboxTemplate.bind({});
304
+ toastInLightbox.args = { type: 'confirmation', message: 'This is a toast in a lightbox.' };