@kiva/kv-components 3.42.0 → 3.44.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/CHANGELOG.md +25 -0
- package/package.json +2 -2
- package/tests/unit/specs/components/KvLineGraph.spec.js +58 -0
- package/vue/KvClassicLoanCard.vue +17 -2
- package/vue/KvLineGraph.vue +128 -0
- package/vue/KvLoanTag.vue +6 -0
- package/vue/stories/KvClassicLoanCard.stories.js +23 -0
- package/vue/stories/KvLineGraph.stories.js +52 -0
- package/vue/stories/KvLoanTag.stories.js +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [3.44.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.43.0...@kiva/kv-components@3.44.0) (2023-09-19)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* increase impact visibility for lse loans ([#298](https://github.com/kiva/kv-ui-elements/issues/298)) ([f9dc1e4](https://github.com/kiva/kv-ui-elements/commit/f9dc1e47f25a68f8e3bd745abfc2338b376bd848))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [3.43.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.42.0...@kiva/kv-components@3.43.0) (2023-09-15)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* added label support ([c016a68](https://github.com/kiva/kv-ui-elements/commit/c016a683ae38ca5af66fa86576d88abd0066d719))
|
|
23
|
+
* line graph can now just take values and normalization happens in component ([3f706be](https://github.com/kiva/kv-ui-elements/commit/3f706be31b288b64e4a566746bd7d0530ce25650))
|
|
24
|
+
* ported over Shua's line graph using clip path and made slight CSS tweaks to match mocks ([56ac312](https://github.com/kiva/kv-ui-elements/commit/56ac312924eecdf56e2d9d99082643e6d42f7765))
|
|
25
|
+
* simple unit tests and small fixes related to tests ([43700fa](https://github.com/kiva/kv-ui-elements/commit/43700faf6c1f0c76432107c3a658539bd3bf6c7b))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
6
31
|
# [3.42.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.41.0...@kiva/kv-components@3.42.0) (2023-09-14)
|
|
7
32
|
|
|
8
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kiva/kv-components",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.44.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -75,5 +75,5 @@
|
|
|
75
75
|
"optional": true
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "de7cefc4bfbacb12fbabf0c4b201319acaba37d3"
|
|
79
79
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { render } from '@testing-library/vue';
|
|
2
|
+
import { axe } from 'jest-axe';
|
|
3
|
+
import KvLineGraph from '../../../../vue/KvLineGraph.vue';
|
|
4
|
+
|
|
5
|
+
describe('KvLineGraph', () => {
|
|
6
|
+
const points = [
|
|
7
|
+
{ value: 10 },
|
|
8
|
+
{ value: 20 },
|
|
9
|
+
{ value: 50 },
|
|
10
|
+
{ value: 60 },
|
|
11
|
+
{ value: 80 },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const pointsWithLabels = [
|
|
15
|
+
{ value: 10, label: '2014' },
|
|
16
|
+
{ value: 20, label: '2015' },
|
|
17
|
+
{ value: 50, label: '2016' },
|
|
18
|
+
{ value: 60, label: '2017' },
|
|
19
|
+
{ value: 80, label: '2018' },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
it('should render chart with points', () => {
|
|
23
|
+
const { container } = render(KvLineGraph, { props: { points } });
|
|
24
|
+
const pointElements = container.querySelectorAll('span');
|
|
25
|
+
|
|
26
|
+
expect(pointElements.length).toBe(points.length);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should render chart with value labels', () => {
|
|
30
|
+
const { container } = render(KvLineGraph, { props: { points: pointsWithLabels } });
|
|
31
|
+
const pointElements = container.querySelectorAll('span');
|
|
32
|
+
|
|
33
|
+
expect(pointElements.length).toBe(points.length * 2);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should render chart with x-axis label', () => {
|
|
37
|
+
const { container } = render(KvLineGraph, { props: { points, axisLabel: 'People supported over time' } });
|
|
38
|
+
const label = container.querySelectorAll('h4');
|
|
39
|
+
|
|
40
|
+
expect(label.length).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should render chart with value labels and x-axis label', () => {
|
|
44
|
+
const { container } = render(KvLineGraph, { props: { points: pointsWithLabels, axisLabel: 'People supported over time' } });
|
|
45
|
+
const pointElements = container.querySelectorAll('span');
|
|
46
|
+
const label = container.querySelectorAll('h4');
|
|
47
|
+
|
|
48
|
+
expect(pointElements.length).toBe(points.length * 2);
|
|
49
|
+
expect(label.length).toBe(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should have no automated accessibility violations', async () => {
|
|
53
|
+
const { container } = render(KvLineGraph, { props: { points } });
|
|
54
|
+
const results = await axe(container);
|
|
55
|
+
|
|
56
|
+
expect(results).toHaveNoViolations();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -231,6 +231,8 @@ import KvLoanTag from './KvLoanTag.vue';
|
|
|
231
231
|
import KvMaterialIcon from './KvMaterialIcon.vue';
|
|
232
232
|
import KvLoadingPlaceholder from './KvLoadingPlaceholder.vue';
|
|
233
233
|
|
|
234
|
+
const LSE_LOAN_KEY = 'N/A';
|
|
235
|
+
|
|
234
236
|
export default {
|
|
235
237
|
name: 'KvClassicLoanCard',
|
|
236
238
|
components: {
|
|
@@ -438,14 +440,25 @@ export default {
|
|
|
438
440
|
singleParents: !!tags.filter((t) => t.toUpperCase() === 'SINGLE PARENT').length,
|
|
439
441
|
};
|
|
440
442
|
|
|
443
|
+
const isLseLoan = this.loan?.partnerName?.toUpperCase().includes(LSE_LOAN_KEY);
|
|
444
|
+
|
|
441
445
|
// P1 Category
|
|
442
446
|
// Exp limited to: Eco-friendly, Refugees and IDPs, Single Parents
|
|
447
|
+
// Tag as first option for LSE loans
|
|
448
|
+
if (isLseLoan && tags.length) {
|
|
449
|
+
const position = Math.floor(Math.random() * tags.length);
|
|
450
|
+
const tag = tags[position];
|
|
451
|
+
callouts.push(tag);
|
|
452
|
+
}
|
|
453
|
+
|
|
443
454
|
if (!this.categoryPageName) {
|
|
444
|
-
if (categories.ecoFriendly
|
|
455
|
+
if (categories.ecoFriendly
|
|
456
|
+
&& !callouts.filter((c) => c.toUpperCase() === categories.ecoFriendly.toUpperCase()).length) {
|
|
445
457
|
callouts.push('Eco-friendly');
|
|
446
458
|
} else if (categories.refugeesIdps) {
|
|
447
459
|
callouts.push('Refugees and IDPs');
|
|
448
|
-
} else if (categories.singleParents
|
|
460
|
+
} else if (categories.singleParents
|
|
461
|
+
&& !callouts.filter((c) => c.toUpperCase() === categories.singleParents.toUpperCase()).length) {
|
|
449
462
|
callouts.push('Single Parent');
|
|
450
463
|
}
|
|
451
464
|
}
|
|
@@ -482,6 +495,8 @@ export default {
|
|
|
482
495
|
}
|
|
483
496
|
}
|
|
484
497
|
|
|
498
|
+
// Only show one callout for LSE loans
|
|
499
|
+
if (isLseLoan && callouts.length > 1) return [callouts.shift()];
|
|
485
500
|
return callouts;
|
|
486
501
|
},
|
|
487
502
|
},
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tw-h-full tw-w-full tw-p-2.5">
|
|
3
|
+
<figure
|
|
4
|
+
class="tw-w-full tw-relative"
|
|
5
|
+
:style="{ height: graphHeight }"
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
class="tw-w-full tw-h-full tw-bg-marigold-2 tw-opacity-low"
|
|
9
|
+
:style="{ clipPath: `polygon(${shade}, 100% 100%, 0% 100%)` }"
|
|
10
|
+
></div>
|
|
11
|
+
<div
|
|
12
|
+
class="tw-absolute tw-top-0 tw-w-full tw-h-full tw-bg-marigold-2"
|
|
13
|
+
:style="{ clipPath: `polygon(${line})` }"
|
|
14
|
+
></div>
|
|
15
|
+
<span
|
|
16
|
+
v-for="point in normalizedPoints"
|
|
17
|
+
:key="point.x"
|
|
18
|
+
class="
|
|
19
|
+
tw-absolute
|
|
20
|
+
tw-w-2
|
|
21
|
+
tw-h-2
|
|
22
|
+
tw-border
|
|
23
|
+
tw-border-white
|
|
24
|
+
tw-bg-marigold-2
|
|
25
|
+
tw-rounded-full
|
|
26
|
+
"
|
|
27
|
+
:style="{ left: `${point.x}%`, top: `${point.y}%`, transform: 'translate(-50%, -50%)' }"
|
|
28
|
+
></span>
|
|
29
|
+
<template v-for="point in normalizedPoints">
|
|
30
|
+
<span
|
|
31
|
+
v-if="point.label"
|
|
32
|
+
:key="point.label"
|
|
33
|
+
class="tw-absolute"
|
|
34
|
+
:style="{ left: `${point.x}%`, bottom: '-3rem', transform: 'translate(-50%, -50%)' }"
|
|
35
|
+
>
|
|
36
|
+
{{ point.label }}
|
|
37
|
+
</span>
|
|
38
|
+
</template>
|
|
39
|
+
</figure>
|
|
40
|
+
<h4
|
|
41
|
+
v-if="axisLabel"
|
|
42
|
+
class="tw-text-center"
|
|
43
|
+
:class="{ 'tw-pt-1': !hasValueLabels, 'tw-pt-6': hasValueLabels }"
|
|
44
|
+
>
|
|
45
|
+
{{ axisLabel }}
|
|
46
|
+
</h4>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
import { computed, toRefs } from 'vue-demi';
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
props: {
|
|
55
|
+
/**
|
|
56
|
+
* Array of objects like [{ value: 10, label: '2014' }, { value: 20, label: '2015' }]
|
|
57
|
+
*/
|
|
58
|
+
points: {
|
|
59
|
+
type: Array,
|
|
60
|
+
required: true,
|
|
61
|
+
},
|
|
62
|
+
/**
|
|
63
|
+
* The optional label to show below the graph on the x-axis
|
|
64
|
+
*/
|
|
65
|
+
axisLabel: {
|
|
66
|
+
type: String,
|
|
67
|
+
default: '',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
setup(props) {
|
|
71
|
+
const { points, axisLabel } = toRefs(props);
|
|
72
|
+
|
|
73
|
+
// Get step to use on x-axis
|
|
74
|
+
const xIncrement = Math.round(100 / (points.value.length - 1));
|
|
75
|
+
|
|
76
|
+
// Find the largest value to be used as the scale of the graph
|
|
77
|
+
const largestY = computed(() => {
|
|
78
|
+
return points.value.reduce((prev, current) => {
|
|
79
|
+
return prev > current.value ? prev : current.value;
|
|
80
|
+
}, 0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Find the largest value to be used as the scale of the graph
|
|
84
|
+
const hasValueLabels = computed(() => {
|
|
85
|
+
return points.value.reduce((prev, current) => {
|
|
86
|
+
return prev || !!current.label;
|
|
87
|
+
}, false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Convert single values to points using increment and largest value
|
|
91
|
+
const normalizedPoints = computed(() => {
|
|
92
|
+
return points.value.reduce((prev, next, i) => {
|
|
93
|
+
prev.push({
|
|
94
|
+
x: i * xIncrement,
|
|
95
|
+
y: 100 - ((next.value / largestY.value) * 100),
|
|
96
|
+
label: next.label,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return prev;
|
|
100
|
+
}, []);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Used for drawing the shading under the line
|
|
104
|
+
const shade = computed(() => (normalizedPoints.value.map(({ x, y }) => `${x}% ${y}%`).join(',')));
|
|
105
|
+
|
|
106
|
+
// Used for drawing the line
|
|
107
|
+
const line = computed(() => {
|
|
108
|
+
const topLine = normalizedPoints.value.map(({ x, y }) => `${x}% ${y + 0.3}%`).join(',');
|
|
109
|
+
const bottomLine = [...normalizedPoints.value].reverse().map(({ x, y }) => `${x}% ${y - 0.3}%`).join(',');
|
|
110
|
+
return `${topLine}, ${bottomLine}`;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const graphHeight = computed(() => {
|
|
114
|
+
const labelSpace = (axisLabel.value ? 2 : 0) + (hasValueLabels.value ? 2 : 0);
|
|
115
|
+
|
|
116
|
+
return `calc(100% - ${labelSpace}rem)`;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
hasValueLabels,
|
|
121
|
+
graphHeight,
|
|
122
|
+
normalizedPoints,
|
|
123
|
+
shade,
|
|
124
|
+
line,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
</script>
|
package/vue/KvLoanTag.vue
CHANGED
|
@@ -17,6 +17,8 @@ import { differenceInDays, parseISO } from 'date-fns';
|
|
|
17
17
|
import numeral from 'numeral';
|
|
18
18
|
import KvCountdownTimer from './KvCountdownTimer.vue';
|
|
19
19
|
|
|
20
|
+
const LSE_LOAN_KEY = 'N/A';
|
|
21
|
+
|
|
20
22
|
export default {
|
|
21
23
|
name: 'KvLoanTag',
|
|
22
24
|
components: {
|
|
@@ -55,6 +57,10 @@ export default {
|
|
|
55
57
|
return null;
|
|
56
58
|
},
|
|
57
59
|
tagText() {
|
|
60
|
+
const partnerName = this.loan?.partnerName ?? '';
|
|
61
|
+
if (partnerName.toUpperCase().includes(LSE_LOAN_KEY)) {
|
|
62
|
+
return 'High community impact';
|
|
63
|
+
}
|
|
58
64
|
switch (this.variation) {
|
|
59
65
|
case 'almost-funded': return 'Almost funded';
|
|
60
66
|
case 'matched-loan': return `${this.matchRatio + 1}x matching by ${this.loan?.matchingText}`;
|
|
@@ -182,6 +182,29 @@ export const LongCallouts = story({
|
|
|
182
182
|
photoPath,
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
export const LseLoan = story({
|
|
186
|
+
loanId: loan.id,
|
|
187
|
+
loan: {
|
|
188
|
+
...loan,
|
|
189
|
+
loanFundraisingInfo: {
|
|
190
|
+
fundedAmount: '950.00',
|
|
191
|
+
isExpiringSoon: false,
|
|
192
|
+
reservedAmount: '0.00',
|
|
193
|
+
},
|
|
194
|
+
partnerName: 'N/A, direct to Novulis',
|
|
195
|
+
tags: [
|
|
196
|
+
'user_favorite',
|
|
197
|
+
'#Woman-Owned Business',
|
|
198
|
+
'#Animals',
|
|
199
|
+
'#Repeat Borrower',
|
|
200
|
+
'#Supporting Family',
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
kvTrackFunction,
|
|
204
|
+
photoPath,
|
|
205
|
+
showTags: true,
|
|
206
|
+
});
|
|
207
|
+
|
|
185
208
|
export const Bookmarked = story({
|
|
186
209
|
loanId: loan.id,
|
|
187
210
|
loan,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import KvLineGraph from '../KvLineGraph.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'KvLineGraph',
|
|
5
|
+
component: KvLineGraph,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const Template = (_args, { argTypes }) => ({
|
|
9
|
+
props: Object.keys(argTypes),
|
|
10
|
+
components: {
|
|
11
|
+
KvLineGraph,
|
|
12
|
+
},
|
|
13
|
+
template: `
|
|
14
|
+
<div style="height: 500px; width: 500px;">
|
|
15
|
+
<kv-line-graph :points="points" :axis-label="axisLabel" />
|
|
16
|
+
</div>
|
|
17
|
+
`,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const points = [
|
|
21
|
+
{ value: 10 },
|
|
22
|
+
{ value: 20 },
|
|
23
|
+
{ value: 50 },
|
|
24
|
+
{ value: 60 },
|
|
25
|
+
{ value: 80 },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const pointsWithLabels = [
|
|
29
|
+
{ value: 10, label: '2014' },
|
|
30
|
+
{ value: 20, label: '2015' },
|
|
31
|
+
{ value: 50, label: '2016' },
|
|
32
|
+
{ value: 60, label: '2017' },
|
|
33
|
+
{ value: 80, label: '2018' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export const Default = Template.bind();
|
|
37
|
+
Default.args = { points };
|
|
38
|
+
|
|
39
|
+
export const AxisLabel = Template.bind();
|
|
40
|
+
AxisLabel.args = {
|
|
41
|
+
points,
|
|
42
|
+
axisLabel: 'People supported over time',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const ValueLabels = Template.bind();
|
|
46
|
+
ValueLabels.args = { points: pointsWithLabels };
|
|
47
|
+
|
|
48
|
+
export const AllLabels = Template.bind();
|
|
49
|
+
AllLabels.args = {
|
|
50
|
+
points: pointsWithLabels,
|
|
51
|
+
axisLabel: 'People supported over time',
|
|
52
|
+
};
|
|
@@ -53,3 +53,15 @@ export const Matched = story({
|
|
|
53
53
|
},
|
|
54
54
|
kvTrackFunction,
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
export const LseLoan = story({
|
|
58
|
+
loan: {
|
|
59
|
+
matchingText: 'Ebay',
|
|
60
|
+
matchRatio: 1,
|
|
61
|
+
plannedExpirationDate: nextWeek.toISOString(),
|
|
62
|
+
loanAmount: 199,
|
|
63
|
+
loanFundraisingInfo: { fundedAmount: 0, reservedAmount: 0 },
|
|
64
|
+
partnerName: 'N/A, direct to Novulis',
|
|
65
|
+
},
|
|
66
|
+
kvTrackFunction,
|
|
67
|
+
});
|