@kiva/kv-components 3.93.0 → 3.95.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 CHANGED
@@ -3,6 +3,38 @@
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.95.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.94.0...@kiva/kv-components@3.95.0) (2024-08-28)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * add story for long borrower name ([da58fc9](https://github.com/kiva/kv-ui-elements/commit/da58fc9a078626577370eea41a0d9336edc2c74e))
12
+ * allow new card to be pull width ([59488dc](https://github.com/kiva/kv-ui-elements/commit/59488dc7e9550d8df38f73fe0b859844e65c6576))
13
+ * fully funded card ([17a9156](https://github.com/kiva/kv-ui-elements/commit/17a9156fc8003f2a19feb030cfe1e1ba369222dc))
14
+ * general new loan card styling tweaks ([dac3bb1](https://github.com/kiva/kv-ui-elements/commit/dac3bb14d62c97b04e7979e0c512edc4c90634c8))
15
+ * hide country for US loans ([5353e0f](https://github.com/kiva/kv-ui-elements/commit/5353e0f9b1678d013becf7775bb4c408cf72887a))
16
+ * one more small styling change ([042da40](https://github.com/kiva/kv-ui-elements/commit/042da402e089805260f4e465c8d237baf6bfe642))
17
+
18
+
19
+ ### Features
20
+
21
+ * add emojis and colors to loan tags ([8d002c2](https://github.com/kiva/kv-ui-elements/commit/8d002c25a243c305fae5113305a604c2f34746e4))
22
+
23
+
24
+
25
+
26
+
27
+ # [3.94.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.93.0...@kiva/kv-components@3.94.0) (2024-08-23)
28
+
29
+
30
+ ### Features
31
+
32
+ * charts added to library ([#447](https://github.com/kiva/kv-ui-elements/issues/447)) ([575bd80](https://github.com/kiva/kv-ui-elements/commit/575bd80fb1700a38732a2b8cafbeb5cc5374764b))
33
+
34
+
35
+
36
+
37
+
6
38
  # [3.93.0](https://github.com/kiva/kv-ui-elements/compare/@kiva/kv-components@3.92.1...@kiva/kv-components@3.93.0) (2024-08-22)
7
39
 
8
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kiva/kv-components",
3
- "version": "3.93.0",
3
+ "version": "3.95.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -71,6 +71,8 @@
71
71
  "moment": "^2.29.4",
72
72
  "nanoid": "^3.1.23",
73
73
  "numeral": "^2.0.6",
74
+ "popper.js": "^1.16.1",
75
+ "treemap-squarify": "^1.0.1",
74
76
  "vue-demi": "^0.14.7"
75
77
  },
76
78
  "peerDependencies": {
@@ -82,5 +84,5 @@
82
84
  "optional": true
83
85
  }
84
86
  },
85
- "gitHead": "56fc9a0c3f8657e2bc69c6acecf01009ec86ee57"
87
+ "gitHead": "5f8bc910fcf78e8ea410172056d9172c55dc80c0"
86
88
  }
package/utils/Alea.js ADDED
@@ -0,0 +1,92 @@
1
+ /* eslint-disable no-plusplus, no-param-reassign, no-return-assign, no-bitwise, prefer-rest-params, func-names */
2
+
3
+ /**
4
+ * The hash function used for Alea pseudo random number generator
5
+ *
6
+ * Johannes Baagøe <baagoe@baagoe.com>, 2010
7
+ * Licensed under the MIT license
8
+ *
9
+ * {@link https://github.com/nquinlan/better-random-numbers-for-javascript-mirror}
10
+ *
11
+ * @returns The hash of the provided data
12
+ */
13
+ export function Mash() {
14
+ let n = 0xefc8249d;
15
+
16
+ const mash = function (data) {
17
+ data = data.toString();
18
+ for (let i = 0; i < data.length; i++) {
19
+ n += data.charCodeAt(i);
20
+ let h = 0.02519603282416938 * n;
21
+ n = h >>> 0;
22
+ h -= n;
23
+ h *= n;
24
+ n = h >>> 0;
25
+ h -= n;
26
+ n += h * 0x100000000; // 2^32
27
+ }
28
+ return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
29
+ };
30
+
31
+ mash.version = 'Mash 0.9';
32
+
33
+ return mash;
34
+ }
35
+
36
+ /**
37
+ * Pseudo random number generator that returns a repeatable set of random numbers when supplied matching seeds
38
+ *
39
+ * Johannes Baagøe <baagoe@baagoe.com>, 2010
40
+ * Licensed under the MIT license
41
+ *
42
+ * {@link https://github.com/nquinlan/better-random-numbers-for-javascript-mirror}
43
+ *
44
+ * @returns The seeded generator function
45
+ */
46
+ export default function Alea() {
47
+ return (function (args) {
48
+ let s0 = 0;
49
+ let s1 = 0;
50
+ let s2 = 0;
51
+ let c = 1;
52
+
53
+ if (args.length === 0) {
54
+ args = [+new Date()];
55
+ }
56
+
57
+ let mash = Mash();
58
+ s0 = mash(' ');
59
+ s1 = mash(' ');
60
+ s2 = mash(' ');
61
+
62
+ for (let i = 0; i < args.length; i++) {
63
+ s0 -= mash(args[i]);
64
+ if (s0 < 0) {
65
+ s0 += 1;
66
+ }
67
+ s1 -= mash(args[i]);
68
+ if (s1 < 0) {
69
+ s1 += 1;
70
+ }
71
+ s2 -= mash(args[i]);
72
+ if (s2 < 0) {
73
+ s2 += 1;
74
+ }
75
+ }
76
+
77
+ mash = null;
78
+
79
+ const random = function () {
80
+ const t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
81
+ s0 = s1;
82
+ s1 = s2;
83
+ return s2 = t - (c = t | 0);
84
+ };
85
+
86
+ random.version = 'Alea 0.9';
87
+
88
+ random.args = args;
89
+
90
+ return random;
91
+ }(Array.prototype.slice.call(arguments)));
92
+ }
package/utils/loanCard.js CHANGED
@@ -21,6 +21,7 @@ export function loanCardComputedProperties(props) {
21
21
  loan,
22
22
  categoryPageName,
23
23
  customCallouts,
24
+ hideUnitedStatesText,
24
25
  } = toRefs(props);
25
26
 
26
27
  const tag = computed(() => (externalLinks.value ? 'a' : 'router-link'));
@@ -49,7 +50,9 @@ export function loanCardComputedProperties(props) {
49
50
 
50
51
  const formattedLocation = computed(() => {
51
52
  if (distributionModel.value === 'direct') {
52
- return `${city.value}, ${state.value}, ${countryName.value}`;
53
+ const countryText = hideUnitedStatesText?.value && countryName.value.toLowerCase() === 'united states'
54
+ ? '' : `, ${countryName.value}`;
55
+ return `${city.value}, ${state.value}${countryText}`;
53
56
  }
54
57
  if (countryName.value === 'Puerto Rico') {
55
58
  return `${city.value}, PR`;
@@ -0,0 +1,22 @@
1
+ // Attach a touchstart event handler to the immediate children of the body.
2
+ // Useful for capturing the user tapping outside of a target element.
3
+ export function onBodyTouchstart(handler) {
4
+ [...document.body.children].forEach((child) => child.addEventListener('touchstart', handler));
5
+ }
6
+
7
+ // Remove a touchstart event handler from the immediate children of the body
8
+ export function offBodyTouchstart(handler) {
9
+ [...document.body.children].forEach((child) => child.removeEventListener('touchstart', handler));
10
+ }
11
+
12
+ // Returns true if the event target is any of the given elements or if the event target
13
+ // is contained by any of the given elements.
14
+ export function isTargetElement(event, elements) {
15
+ const els = Array.isArray(elements) ? elements : [elements];
16
+ for (let i = 0; i < els.length; i += 1) {
17
+ if (els[i] === event.target || els[i].contains(event.target)) {
18
+ return true;
19
+ }
20
+ }
21
+ return false;
22
+ }
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <div
3
- class="tw-flex tw-flex-col tw-bg-white tw-rounded tw-w-full tw-pb-1"
3
+ class="tw-flex tw-flex-col tw-bg-white tw-rounded tw-w-full tw-pb-2"
4
4
  :class="{ 'tw-pointer-events-none' : isLoading }"
5
5
  data-testid="loan-card"
6
- style="box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);"
7
- :style="{ minWidth: '230px', maxWidth: '20.5rem' }"
6
+ style="box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.38);"
7
+ :style="{ minWidth: '230px', maxWidth: cardWidth }"
8
8
  >
9
9
  <div class="tw-grow">
10
10
  <div class="loan-card-active-hover">
@@ -63,9 +63,9 @@
63
63
  style="padding: 2px 6px;"
64
64
  >
65
65
  <kv-flag
66
- class="tw-mr-0.5"
66
+ class="tw-ml-0.5 tw-mr-1"
67
67
  :country="countryCode"
68
- width-override="0.725rem"
68
+ width-override="16px"
69
69
  hide-border
70
70
  />
71
71
  {{ formattedLocation }}
@@ -82,13 +82,14 @@
82
82
  :style="{ width: '60%', height: '1.75rem', 'border-radius': '500rem' }"
83
83
  />
84
84
 
85
- <h2
85
+ <h3
86
86
  class="loan-card-name"
87
- :class="{'tw-text-center': borrowerName.length < 20}"
87
+ :class="{ 'tw-text-center': borrowerName.length < 20 }"
88
+ style="font-size: 28px;"
88
89
  @click="clickReadMore('Name', $event)"
89
90
  >
90
91
  {{ borrowerName }}
91
- </h2>
92
+ </h3>
92
93
 
93
94
  <!-- Loan tag -->
94
95
 
@@ -99,17 +100,17 @@
99
100
  <kv-loading-placeholder
100
101
  v-if="isLoading || typeof loanCallouts === 'undefined'"
101
102
  class="tw-mt-0.5 tw-mb-1"
102
- :style="{ width: '20%', height: '1.75rem', 'border-radius': '500rem' }"
103
+ :style="{ width: '20%', height: '1.65rem', 'border-radius': '500rem' }"
103
104
  />
104
105
  <kv-loading-placeholder
105
106
  v-if="isLoading || typeof loanCallouts === 'undefined'"
106
107
  class="tw-mt-0.5 tw-mb-1"
107
- :style="{ width: '20%', height: '1.75rem', 'border-radius': '500rem' }"
108
+ :style="{ width: '20%', height: '1.65rem', 'border-radius': '500rem' }"
108
109
  />
109
110
  <kv-loading-placeholder
110
111
  v-if="isLoading || typeof loanCallouts === 'undefined'"
111
112
  class="tw-mt-0.5 tw-mb-1"
112
- :style="{ width: '20%', height: '1.75rem', 'border-radius': '500rem' }"
113
+ :style="{ width: '20%', height: '1.65rem', 'border-radius': '500rem' }"
113
114
  />
114
115
  </div>
115
116
 
@@ -139,7 +140,7 @@
139
140
  >
140
141
  <!-- Loan use -->
141
142
  <div
142
- class="tw-pt-1 tw-px-1"
143
+ class="tw-pt-0.5 tw-px-2"
143
144
  :class="{'tw-mb-1.5': !isLoading}"
144
145
  >
145
146
  <div
@@ -172,7 +173,7 @@
172
173
  </div>
173
174
  </div>
174
175
 
175
- <div class="tw-px-1">
176
+ <div class="tw-px-2">
176
177
  <!-- Fundraising -->
177
178
  <div
178
179
  v-if="!hasProgressData"
@@ -215,21 +216,28 @@
215
216
  </component>
216
217
  </div>
217
218
 
218
- <!-- Loan Match -->
219
+ <!-- Loan Tag -->
219
220
  <kv-loading-placeholder
220
221
  v-if="isLoading"
221
- class="tw-rounded tw-mx-auto tw-mt-1"
222
+ class="tw-rounded tw-mx-auto tw-mt-2"
222
223
  :style="{ width: '9rem', height: '1rem' }"
223
224
  />
224
225
 
225
- <div class="tw-pb-2.5">
226
- <kv-loan-tag
227
- v-if="matchingText"
228
- :loan="loan"
229
- variation="matched-loan"
230
- class="tw-text-center !tw-text-brand"
231
- />
226
+ <div
227
+ v-else-if="isFunded"
228
+ class="tw-bg-eco-green-1 tw-text-action tw-text-center tw-py-1.5"
229
+ style="font-weight: bold; font-size: 13px; border-radius: 8px;"
230
+ >
231
+ 🎉 Fully funded!
232
232
  </div>
233
+
234
+ <kv-loan-tag
235
+ v-else
236
+ :loan="loan"
237
+ :use-expanded-styles="true"
238
+ style="font-size: 15px;"
239
+ class="tw-text-center tw-pt-2"
240
+ />
233
241
  </div>
234
242
  </div>
235
243
  </template>
@@ -300,6 +308,10 @@ export default {
300
308
  type: Object,
301
309
  default: undefined,
302
310
  },
311
+ useFullWidth: {
312
+ type: Boolean,
313
+ default: false,
314
+ },
303
315
  },
304
316
  setup(props, { emit }) {
305
317
  const {
@@ -324,7 +336,7 @@ export default {
324
336
  state,
325
337
  tag,
326
338
  unreservedAmount,
327
- } = loanCardComputedProperties(props);
339
+ } = loanCardComputedProperties({ ...props, hideUnitedStatesText: true });
328
340
 
329
341
  const {
330
342
  clickReadMore,
@@ -356,6 +368,9 @@ export default {
356
368
  };
357
369
  },
358
370
  computed: {
371
+ cardWidth() {
372
+ return this.useFullWidth ? '100%' : '20.5rem';
373
+ },
359
374
  imageAspectRatio() {
360
375
  return 5 / 8;
361
376
  },
@@ -374,8 +389,8 @@ export default {
374
389
  const amount = this.loan?.loanFundraisingInfo?.fundedAmount ?? 0;
375
390
  return numeral(parseFloat(amount)).format('$0,0');
376
391
  },
377
- matchingText() {
378
- return this.loan?.matchingText ?? '';
392
+ isFunded() {
393
+ return this.loan?.status === 'funded' || parseFloat(this.unreservedAmount) === 0;
379
394
  },
380
395
  },
381
396
  };
@@ -383,7 +398,7 @@ export default {
383
398
 
384
399
  <style lang="postcss" scoped>
385
400
  .loan-callouts >>> span {
386
- @apply !tw-bg-transparent tw-text-brand;
401
+ @apply !tw-bg-transparent tw-text-action;
387
402
  }
388
403
  .loan-card-use:hover,
389
404
  .loan-card-use:focus {
package/vue/KvLoanTag.vue CHANGED
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  v-if="!!variation"
4
4
  class="tw-text-small tw-font-medium tw-pt-0.5 tw-line-clamp-1"
5
- style="color: #CE4A00;"
5
+ :style="{ color: tagColor }"
6
6
  >
7
7
  {{ tagText }}
8
8
  <kv-countdown-timer
@@ -29,6 +29,10 @@ export default {
29
29
  type: Object,
30
30
  required: true,
31
31
  },
32
+ useExpandedStyles: {
33
+ type: Boolean,
34
+ default: false,
35
+ },
32
36
  },
33
37
  data() {
34
38
  return {
@@ -59,11 +63,22 @@ export default {
59
63
  },
60
64
  tagText() {
61
65
  switch (this.variation) {
62
- case 'lse-loan': return 'High community impact';
63
- case 'almost-funded': return 'Almost funded';
64
- case 'matched-loan': return `${this.matchRatio + 1}x matching by ${this.loan?.matchingText}`;
65
- default: return 'Ending soon: ';
66
+ case 'lse-loan': return `${this.useExpandedStyles ? '⚡ ' : ''}High community impact`;
67
+ case 'almost-funded': return `${this.useExpandedStyles ? '💸 ' : ''}Almost funded`;
68
+ // eslint-disable-next-line max-len
69
+ case 'matched-loan': return `${this.useExpandedStyles ? '🤝 ' : ''}${this.matchRatio + 1}x matching by ${this.loan?.matchingText}`;
70
+ default: return `${this.useExpandedStyles ? '⏰ ' : ''}Ending soon: `;
71
+ }
72
+ },
73
+ tagColor() {
74
+ if (this.useExpandedStyles) {
75
+ switch (this.variation) {
76
+ case 'almost-funded': return '#AF741C';
77
+ case 'ending-soon': return '#CE2626';
78
+ default: return '#2B7C5F';
79
+ }
66
80
  }
81
+ return '#CE4A00';
67
82
  },
68
83
  matchRatio() {
69
84
  return this.loan?.matchRatio;
@@ -0,0 +1,232 @@
1
+ <template>
2
+ <figure
3
+ class="pie-chart tw-flex tw-flex-col md:tw-flex-row tw-items-center tw-justify-center tw-gap-2"
4
+ @mouseleave="activeSlice = null"
5
+ >
6
+ <!-- pie chart -->
7
+ <div class="tw-relative tw-h-full">
8
+ <div
9
+ v-if="loading"
10
+ class="pie-placeholder tw-h-full tw-p-2.5"
11
+ >
12
+ <div class="tw-overflow-hidden tw-rounded-full tw-h-full">
13
+ <kv-loading-placeholder />
14
+ </div>
15
+ </div>
16
+ <svg
17
+ v-else
18
+ class="tw-h-full"
19
+ viewBox="0 0 32 32"
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ xmlns:xlink="http://www.w3.org/1999/xlink"
22
+ >
23
+ <path
24
+ v-for="(slice, index) in slices"
25
+ :key="index"
26
+ class="tw-origin-center tw-transition-transform"
27
+ :style="activeSlice === slice ? { transform: 'scale(1.1)' } : {}"
28
+ :d="slice.path"
29
+ :stroke="slice.color"
30
+ :stroke-width="lineWidth"
31
+ fill="none"
32
+ @mouseenter="activeSlice = slice"
33
+ @click="activeSlice = slice"
34
+ />
35
+ </svg>
36
+ <!-- active slice -->
37
+ <div
38
+ v-if="activeSlice"
39
+ class="
40
+ tw-absolute tw-top-1/2 tw-left-1/2 -tw-translate-x-1/2 -tw-translate-y-1/2
41
+ tw-flex tw-flex-col tw-items-center tw-text-center"
42
+ style="max-width: 8.5rem;"
43
+ >
44
+ <div class="tw-font-medium tw-line-clamp-4">
45
+ {{ activeSlice.label }}
46
+ </div>
47
+ <div>
48
+ {{ activeSlice.value }} ({{ activeSlicePercent }})
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <!-- key -->
53
+ <div
54
+ style="width: 14rem; height: 85%;"
55
+ class="tw-flex tw-flex-col tw-justify-between"
56
+ >
57
+ <ol class="tw-pl-4 md:tw-pl-0">
58
+ <li
59
+ v-for="(slice, index) in slices.slice(pageIndex * slicesPerPage, (pageIndex + 1) * slicesPerPage)"
60
+ :key="index"
61
+ class="tw-flex tw-items-center"
62
+ @mouseenter="activeSlice = slice"
63
+ @click="activeSlice = slice"
64
+ >
65
+ <div
66
+ class="tw-w-2 tw-h-2 tw-mr-1 tw-rounded-full tw-flex-none"
67
+ :style="{ backgroundColor: slice.color }"
68
+ ></div>
69
+ <span class="tw-truncate">
70
+ {{ slice.label }}
71
+ </span>
72
+ </li>
73
+ </ol>
74
+ <!-- paging controls -->
75
+ <div
76
+ v-if="pageCount > 1"
77
+ class="tw-flex tw-justify-center md:tw-justify-start"
78
+ >
79
+ <button
80
+ :disabled="pageIndex === 0"
81
+ class="tw-font-medium tw-p-0.5 disabled:tw-opacity-low"
82
+ @click="prevPage"
83
+ >
84
+ &lt;
85
+ </button>
86
+ {{ pageIndex + 1 }} / {{ pageCount }}
87
+ <button
88
+ :disabled="pageIndex === pageCount - 1"
89
+ class="tw-font-medium tw-p-0.5 disabled:tw-opacity-low"
90
+ @click="nextPage"
91
+ >
92
+ &gt;
93
+ </button>
94
+ </div>
95
+ </div>
96
+ </figure>
97
+ </template>
98
+
99
+ <script>
100
+ import numeral from 'numeral';
101
+ import { ref, toRefs, computed } from 'vue-demi';
102
+ import Alea from '../utils/Alea';
103
+ import KvLoadingPlaceholder from './KvLoadingPlaceholder.vue';
104
+
105
+ // convenience function to get point on circumference of a given circle (from https://codepen.io/grieve/pen/xwGMJp)
106
+ function circumPointFromAngle(cx, cy, r, a) {
107
+ return [
108
+ cx + r * Math.cos(a),
109
+ cy + r * Math.sin(a),
110
+ ];
111
+ }
112
+
113
+ export default {
114
+ name: 'PieChartFigure',
115
+ components: {
116
+ KvLoadingPlaceholder,
117
+ },
118
+ props: {
119
+ loading: {
120
+ type: Boolean,
121
+ default: true,
122
+ },
123
+ values: {
124
+ type: Array,
125
+ default: () => ([]),
126
+ },
127
+ },
128
+ setup(props) {
129
+ const {
130
+ values,
131
+ } = toRefs(props);
132
+
133
+ const svgSize = ref(32);
134
+ const lineWidth = ref(5);
135
+ const activeSlice = ref(null);
136
+ const pageIndex = ref(0);
137
+ const slicesPerPage = ref(10);
138
+
139
+ const activeSlicePercent = computed(() => {
140
+ return activeSlice.value ? numeral(activeSlice.value.percent).format('0.00%') : '';
141
+ });
142
+
143
+ const radius = computed(() => {
144
+ return (svgSize.value / 2) - (lineWidth.value / 2) - 2;
145
+ });
146
+
147
+ const center = computed(() => {
148
+ return svgSize.value / 2;
149
+ });
150
+
151
+ const pickColor = (index) => {
152
+ const rng = new Alea('loans', index, 'kiva');
153
+ let color = '#';
154
+ for (let i = 0; i < 3; i += 1) {
155
+ color += Math.floor(rng() * 256).toString(16).padStart(2, '0');
156
+ }
157
+ return color;
158
+ };
159
+
160
+ const slices = computed(() => {
161
+ const slicesArr = [];
162
+ // center point
163
+ const cX = center.value;
164
+ const cY = cX;
165
+ // radius
166
+ const r = radius.value;
167
+ // starting angle
168
+ let start = -0.25;
169
+ // loop through each value and create a pie slice path
170
+ for (let i = 0; i < values.value.length; i += 1) {
171
+ const value = values.value[i];
172
+ const end = start + value.percent;
173
+ const [startX, startY] = circumPointFromAngle(cX, cY, r, start * Math.PI * 2);
174
+ const [endX, endY] = circumPointFromAngle(cX, cY, r, end * Math.PI * 2);
175
+ const largeArc = value.percent > 0.5 ? 1 : 0;
176
+ // Draw just the outer arc of the slice
177
+ const path = `M ${startX} ${startY} A ${r} ${r} 0 ${largeArc} 1 ${endX} ${endY}`;
178
+ slicesArr.push({
179
+ ...value,
180
+ path,
181
+ color: pickColor(i),
182
+ });
183
+ start = end;
184
+ }
185
+ return slicesArr;
186
+ });
187
+
188
+ const pageCount = computed(() => {
189
+ return Math.ceil(slices.value.length / slicesPerPage.value);
190
+ });
191
+
192
+ const prevPage = () => {
193
+ if (pageIndex.value > 0) {
194
+ pageIndex.value -= 1;
195
+ }
196
+ };
197
+ const nextPage = () => {
198
+ if (pageIndex.value < pageCount.value - 1) {
199
+ pageIndex.value += 1;
200
+ }
201
+ };
202
+
203
+ return {
204
+ svgSize,
205
+ lineWidth,
206
+ activeSlice,
207
+ pageIndex,
208
+ slicesPerPage,
209
+ activeSlicePercent,
210
+ radius,
211
+ center,
212
+ slices,
213
+ pageCount,
214
+ prevPage,
215
+ nextPage,
216
+ };
217
+ },
218
+ };
219
+ </script>
220
+
221
+ <style lang="postcss" scoped>
222
+ .pie-chart {
223
+ height: 40rem;
224
+ @screen md {
225
+ height: 20rem;
226
+ }
227
+ }
228
+
229
+ .pie-placeholder {
230
+ width: 20rem;
231
+ }
232
+ </style>