@kiva/kv-components 3.107.0 → 3.107.1
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 +11 -0
- package/dist/components/.storybook/main.js +85 -0
- package/dist/components/.storybook/package.json +3 -0
- package/dist/components/.storybook/preview.js +61 -0
- package/dist/components/.storybook/tailwind.css +5 -0
- package/dist/components/KvAccordionItem.vue +130 -0
- package/dist/components/KvActivityRow.vue +33 -0
- package/dist/components/KvBorrowerImage.vue +179 -0
- package/dist/components/KvButton.vue +287 -0
- package/dist/components/KvCarousel.vue +297 -0
- package/dist/components/KvCartModal.vue +365 -0
- package/dist/components/KvCheckbox.vue +203 -0
- package/dist/components/KvChip.vue +54 -0
- package/dist/components/KvClassicLoanCard.vue +527 -0
- package/dist/components/KvCommentsAdd.vue +135 -0
- package/dist/components/KvCommentsContainer.vue +84 -0
- package/dist/components/KvCommentsHeartButton.vue +70 -0
- package/dist/components/KvCommentsList.vue +68 -0
- package/dist/components/KvCommentsListItem.vue +241 -0
- package/dist/components/KvCommentsReplyButton.vue +52 -0
- package/dist/components/KvContentfulImg.vue +273 -0
- package/dist/components/KvCountdownTimer.vue +59 -0
- package/dist/components/KvExpandable.vue +84 -0
- package/dist/components/KvExpandableQuestion.vue +120 -0
- package/dist/components/KvFlag.vue +120 -0
- package/dist/components/KvGrid.vue +28 -0
- package/dist/components/KvImpactDashboardHeader.vue +40 -0
- package/dist/components/KvInlineActivityCard.vue +55 -0
- package/dist/components/KvInlineActivityFeed.vue +38 -0
- package/dist/components/KvIntroductionLoanCard.vue +446 -0
- package/dist/components/KvLendAmountButton.vue +65 -0
- package/dist/components/KvLendCta.vue +451 -0
- package/dist/components/KvLightbox.vue +334 -0
- package/dist/components/KvLineGraph.vue +128 -0
- package/dist/components/KvLoadingPlaceholder.vue +38 -0
- package/dist/components/KvLoadingSpinner.vue +81 -0
- package/dist/components/KvLoanActivities.vue +268 -0
- package/dist/components/KvLoanBookmark.vue +39 -0
- package/dist/components/KvLoanCallouts.vue +53 -0
- package/dist/components/KvLoanProgressGroup.vue +76 -0
- package/dist/components/KvLoanTag.vue +88 -0
- package/dist/components/KvLoanTeamPick.vue +44 -0
- package/dist/components/KvLoanUse.vue +92 -0
- package/dist/components/KvMap.vue +599 -0
- package/dist/components/KvMaterialIcon.vue +47 -0
- package/dist/components/KvPageContainer.vue +15 -0
- package/dist/components/KvPagination.vue +198 -0
- package/dist/components/KvPieChart.vue +257 -0
- package/dist/components/KvPopper.vue +178 -0
- package/dist/components/KvProgressBar.vue +149 -0
- package/dist/components/KvRadio.vue +198 -0
- package/dist/components/KvSelect.vue +114 -0
- package/dist/components/KvSideSheet.vue +134 -0
- package/dist/components/KvSwitch.vue +143 -0
- package/dist/components/KvTab.vue +90 -0
- package/dist/components/KvTabPanel.vue +64 -0
- package/dist/components/KvTabs.vue +182 -0
- package/dist/components/KvTextInput.vue +247 -0
- package/dist/components/KvTextLink.vue +138 -0
- package/dist/components/KvThemeProvider.vue +122 -0
- package/dist/components/KvToast.vue +221 -0
- package/dist/components/KvTooltip.vue +168 -0
- package/dist/components/KvTreeMapChart.vue +229 -0
- package/dist/components/KvUserAvatar.vue +132 -0
- package/dist/components/KvVerticalCarousel.vue +156 -0
- package/dist/components/KvVotingCard.vue +160 -0
- package/dist/components/KvVotingCardV2.vue +154 -0
- package/dist/components/KvWideLoanCard.vue +432 -0
- package/dist/components/stories/Forms.stories.js +62 -0
- package/dist/components/stories/KvAccordionItem.stories.js +24 -0
- package/dist/components/stories/KvActivityRow.stories.js +25 -0
- package/dist/components/stories/KvBorrowerImage.stories.js +68 -0
- package/dist/components/stories/KvButton.stories.js +144 -0
- package/dist/components/stories/KvCarousel.stories.js +426 -0
- package/dist/components/stories/KvCartModal.stories.js +54 -0
- package/dist/components/stories/KvCheckbox.stories.js +163 -0
- package/dist/components/stories/KvChip.stories.js +43 -0
- package/dist/components/stories/KvClassicLoanCard.stories.js +480 -0
- package/dist/components/stories/KvCommentsAdd.stories.js +32 -0
- package/dist/components/stories/KvCommentsContainer.stories.js +42 -0
- package/dist/components/stories/KvCommentsHeartButton.stories.js +25 -0
- package/dist/components/stories/KvCommentsList.stories.js +39 -0
- package/dist/components/stories/KvCommentsListItem.stories.js +45 -0
- package/dist/components/stories/KvCommentsReplyButton.stories.js +21 -0
- package/dist/components/stories/KvContentfulImg.stories.js +196 -0
- package/dist/components/stories/KvCountdownTimer.stories.js +30 -0
- package/dist/components/stories/KvExpandableQuestion.stories.js +129 -0
- package/dist/components/stories/KvFlag.stories.js +36 -0
- package/dist/components/stories/KvGrid.stories.js +97 -0
- package/dist/components/stories/KvImpactDashboardHeader.stories.js +22 -0
- package/dist/components/stories/KvInlineActivityCard.stories.js +69 -0
- package/dist/components/stories/KvInlineActivityFeed.stories.js +76 -0
- package/dist/components/stories/KvIntroductionLoanCard.stories.js +208 -0
- package/dist/components/stories/KvLendAmountButton.stories.js +31 -0
- package/dist/components/stories/KvLendCta.stories.js +177 -0
- package/dist/components/stories/KvLightbox.stories.js +304 -0
- package/dist/components/stories/KvLineGraph.stories.js +52 -0
- package/dist/components/stories/KvLoadingPlaceholder.stories.js +17 -0
- package/dist/components/stories/KvLoadingSpinner.stories.js +52 -0
- package/dist/components/stories/KvLoanActivities.stories.js +104 -0
- package/dist/components/stories/KvLoanBookmark.stories.js +22 -0
- package/dist/components/stories/KvLoanCallouts.stories.js +22 -0
- package/dist/components/stories/KvLoanProgressGroup.stories.js +29 -0
- package/dist/components/stories/KvLoanTag.stories.js +61 -0
- package/dist/components/stories/KvLoanTeamPick.stories.js +20 -0
- package/dist/components/stories/KvLoanUse.stories.js +60 -0
- package/dist/components/stories/KvMap.stories.js +121 -0
- package/dist/components/stories/KvMaterialIcon.stories.js +201 -0
- package/dist/components/stories/KvPageContainer.stories.js +50 -0
- package/dist/components/stories/KvPagination.stories.js +70 -0
- package/dist/components/stories/KvPieChart.stories.js +47 -0
- package/dist/components/stories/KvProgressBar.stories.js +53 -0
- package/dist/components/stories/KvRadio.stories.js +140 -0
- package/dist/components/stories/KvSelect.stories.js +125 -0
- package/dist/components/stories/KvSideSheet.stories.js +50 -0
- package/dist/components/stories/KvSwitch.stories.js +66 -0
- package/dist/components/stories/KvTabs.stories.js +106 -0
- package/dist/components/stories/KvTextInput.stories.js +194 -0
- package/dist/components/stories/KvTextLink.stories.js +55 -0
- package/dist/components/stories/KvThemeProvider.stories.js +178 -0
- package/dist/components/stories/KvToast.stories.js +117 -0
- package/dist/components/stories/KvTooltip.stories.js +26 -0
- package/dist/components/stories/KvTreeMapChart.stories.js +42 -0
- package/dist/components/stories/KvUserAvatar.stories.js +47 -0
- package/dist/components/stories/KvVerticalCarousel.stories.js +168 -0
- package/dist/components/stories/KvVotingCard.stories.js +33 -0
- package/dist/components/stories/KvVotingCardV2.stories.js +89 -0
- package/dist/components/stories/KvWideLoanCard.stories.js +292 -0
- package/dist/components/stories/StyleguidePrimitives.stories.js +499 -0
- package/dist/components/stories/StyleguideProse.stories.js +215 -0
- package/dist/data/countries-borders.json +1 -0
- package/dist/data/ne_110m_admin_0_countries.json +1 -0
- package/dist/utils/Alea.js +9 -0
- package/dist/utils/attrs.js +7 -0
- package/dist/utils/carousels.js +8 -0
- package/dist/{attrs.js → utils/chunk-3HK4G4NT.js} +1 -0
- package/dist/{loanCard.js → utils/chunk-55HF2ORX.js} +1 -0
- package/dist/{expander.js → utils/chunk-AY3PR5S4.js} +3 -2
- package/dist/{carousels.js → utils/chunk-AZPWOFD5.js} +1 -0
- package/dist/{printing.js → utils/chunk-B5J5WLAH.js} +1 -0
- package/dist/{Alea.js → utils/chunk-GPSH6OPA.js} +2 -1
- package/dist/{scrollLock.js → utils/chunk-HIY5IW65.js} +2 -1
- package/dist/{treemap.js → utils/chunk-MSMZIN54.js} +1 -0
- package/dist/{imageUtils.js → utils/chunk-OXJCCNNW.js} +1 -0
- package/dist/{touchEvents.js → utils/chunk-S3MABILA.js} +3 -2
- package/dist/{mapUtils.js → utils/chunk-VIGEMAKO.js} +5 -4
- package/dist/utils/chunk-YCNMJ4YV.js +37 -0
- package/dist/{loanUtils.js → utils/chunk-YFEC5ODJ.js} +7 -6
- package/dist/utils/expander.js +9 -0
- package/dist/utils/imageUtils.js +9 -0
- package/dist/utils/index.cjs +1118 -0
- package/dist/utils/index.js +166 -0
- package/dist/utils/loanCard.js +9 -0
- package/dist/utils/loanUtils.js +23 -0
- package/dist/utils/mapUtils.js +15 -0
- package/dist/utils/printing.js +9 -0
- package/dist/utils/scrollLock.js +13 -0
- package/dist/{throttle.js → utils/throttle.js} +1 -0
- package/dist/utils/touchEvents.js +11 -0
- package/dist/utils/treemap.js +7 -0
- package/package.json +7 -7
- package/utils/index.js +14 -0
- package/index.js +0 -3
- /package/dist/{Alea.cjs → utils/Alea.cjs} +0 -0
- /package/dist/{attrs.cjs → utils/attrs.cjs} +0 -0
- /package/dist/{carousels.cjs → utils/carousels.cjs} +0 -0
- /package/dist/{chunk-HV3AUBFT.js → utils/chunk-HV3AUBFT.js} +0 -0
- /package/dist/{expander.cjs → utils/expander.cjs} +0 -0
- /package/dist/{imageUtils.cjs → utils/imageUtils.cjs} +0 -0
- /package/dist/{loanCard.cjs → utils/loanCard.cjs} +0 -0
- /package/dist/{loanUtils.cjs → utils/loanUtils.cjs} +0 -0
- /package/dist/{mapUtils.cjs → utils/mapUtils.cjs} +0 -0
- /package/dist/{printing.cjs → utils/printing.cjs} +0 -0
- /package/dist/{scrollLock.cjs → utils/scrollLock.cjs} +0 -0
- /package/dist/{throttle.cjs → utils/throttle.cjs} +0 -0
- /package/dist/{touchEvents.cjs → utils/touchEvents.cjs} +0 -0
- /package/dist/{treemap.cjs → utils/treemap.cjs} +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<figure
|
|
3
|
+
v-if="(width || height) && contentfulSrc"
|
|
4
|
+
class="tw-inline-block tw-not-prose"
|
|
5
|
+
>
|
|
6
|
+
<picture
|
|
7
|
+
class="tw-h-full tw-w-full"
|
|
8
|
+
style="object-fit: inherit;"
|
|
9
|
+
>
|
|
10
|
+
<!-- Set of image sources -->
|
|
11
|
+
<template v-if="sourceSizes.length > 0">
|
|
12
|
+
<!-- browser supports webp -->
|
|
13
|
+
<source
|
|
14
|
+
v-for="(image, index) in sourceSizes"
|
|
15
|
+
:key="'webp-image'+index"
|
|
16
|
+
:media="'('+image.media+')'"
|
|
17
|
+
type="image/webp"
|
|
18
|
+
:width="image.width ? image.width : null"
|
|
19
|
+
:height="image.height ? image.height : null"
|
|
20
|
+
:srcset="`
|
|
21
|
+
${buildUrl(image, 2)}&fit=${fit}&f=${focus}&fm=webp&q=${setQuality(image.width, '2x')} 2x,
|
|
22
|
+
${buildUrl(image)}&fit=${fit}&f=${focus}&fm=webp&q=${setQuality(image.width, '1x')} 1x`"
|
|
23
|
+
>
|
|
24
|
+
<!-- browser doesn't support webp -->
|
|
25
|
+
<!-- eslint-disable max-len -->
|
|
26
|
+
<source
|
|
27
|
+
v-for="(image, index) in sourceSizes"
|
|
28
|
+
:key="'fallback-image'+index"
|
|
29
|
+
:media="'('+image.media+')'"
|
|
30
|
+
:width="image.width ? image.width : null"
|
|
31
|
+
:height="image.height ? image.height : null"
|
|
32
|
+
:srcset="`
|
|
33
|
+
${buildUrl(image, 2)}&fit=${fit}&f=${focus}&fm=${fallbackFormat}&q=${setQuality(image.width, '2x')} 2x,
|
|
34
|
+
${buildUrl(image)}&fit=${fit}&f=${focus}&fm=${fallbackFormat}&q=${setQuality(image.width, '1x')} 1x`"
|
|
35
|
+
>
|
|
36
|
+
<!-- eslint-enable max-len -->
|
|
37
|
+
<!-- browser doesn't support picture element -->
|
|
38
|
+
<!-- eslint-disable max-len -->
|
|
39
|
+
<img
|
|
40
|
+
class="tw-max-w-full tw-max-h-full"
|
|
41
|
+
style="width: inherit; height: inherit; object-fit: inherit;"
|
|
42
|
+
:src="`${buildUrl(width, height)}&fit=${fit}&f=${focus}&fm=${fallbackFormat}&q=${setQuality(width, '1x')}`"
|
|
43
|
+
:alt="caption || alt"
|
|
44
|
+
:loading="loading"
|
|
45
|
+
>
|
|
46
|
+
<!-- eslint-enable max-len -->
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<!-- Single image -->
|
|
50
|
+
<template v-if="sourceSizes.length === 0">
|
|
51
|
+
<!-- browser supports webp -->
|
|
52
|
+
<source
|
|
53
|
+
type="image/webp"
|
|
54
|
+
:srcset="`
|
|
55
|
+
${buildUrl(null, 2)}&fit=${fit}&f=${focus}&fm=webp&q=${setQuality(width, '2x')} 2x,
|
|
56
|
+
${buildUrl()}&fit=${fit}&f=${focus}&fm=webp&q=${setQuality(width, '1x')} 1x`"
|
|
57
|
+
>
|
|
58
|
+
<!-- browser doesn't support webp or browser doesn't support picture element -->
|
|
59
|
+
<img
|
|
60
|
+
class="tw-max-w-full tw-max-h-full"
|
|
61
|
+
style="width: inherit; height: inherit; object-fit: inherit;"
|
|
62
|
+
:srcset="`
|
|
63
|
+
${buildUrl(null, 2)}&fit=${fit}&f=${focus}&fm=${fallbackFormat}&q=${setQuality(width, '2x')} 2x,
|
|
64
|
+
${buildUrl()}&fit=${fit}&f=${focus}&fm=${fallbackFormat}&q=${setQuality(width, '1x')} 1x`"
|
|
65
|
+
:src="`${buildUrl()}&fit=${fit}&f=${focus}&fm=${fallbackFormat}&q=${setQuality(width, '1x')}`"
|
|
66
|
+
:width="width ? width : null"
|
|
67
|
+
:height="height ? height : null"
|
|
68
|
+
:alt="caption || alt"
|
|
69
|
+
:loading="loading"
|
|
70
|
+
>
|
|
71
|
+
</template>
|
|
72
|
+
</picture>
|
|
73
|
+
<figcaption
|
|
74
|
+
v-if="caption"
|
|
75
|
+
class="tw-text-h4 tw-mt-2"
|
|
76
|
+
>
|
|
77
|
+
<span
|
|
78
|
+
class="tw-inline-flex tw-align-text-top"
|
|
79
|
+
aria-hidden="true"
|
|
80
|
+
role="img"
|
|
81
|
+
>
|
|
82
|
+
<svg
|
|
83
|
+
class="tw-h-2 tw-w-2.5"
|
|
84
|
+
viewBox="0 0 20 20"
|
|
85
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
86
|
+
>
|
|
87
|
+
<!-- eslint-disable max-len -->
|
|
88
|
+
<path
|
|
89
|
+
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"
|
|
90
|
+
fill="currentColor"
|
|
91
|
+
/>
|
|
92
|
+
<!-- eslint-enable max-len -->
|
|
93
|
+
</svg>
|
|
94
|
+
</span>
|
|
95
|
+
{{ caption }}
|
|
96
|
+
</figcaption>
|
|
97
|
+
</figure>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<script>
|
|
101
|
+
import { computed, toRefs } from 'vue-demi';
|
|
102
|
+
// Since it's easy for marketing or other to upload massive images to contentful,
|
|
103
|
+
// in order to be performant respectful of our users data plans, and not damage
|
|
104
|
+
// our SEO, we shouldn't send the source image directly to our users.
|
|
105
|
+
|
|
106
|
+
// This component uses to contentful's image query params to:
|
|
107
|
+
// Serve webp with a fallback for older browsers.
|
|
108
|
+
// Offer both 1x and 2x images.
|
|
109
|
+
// Properly size the images and make sure they're compressed.
|
|
110
|
+
// Crop images around focus area.
|
|
111
|
+
// If we have both width and height we pass those attributes to the image to prevent jank.
|
|
112
|
+
// Allow lazy loading via image attribute.
|
|
113
|
+
|
|
114
|
+
export default {
|
|
115
|
+
props: {
|
|
116
|
+
/**
|
|
117
|
+
* Large, uncompressed image url that you get back from contentful.
|
|
118
|
+
* */
|
|
119
|
+
contentfulSrc: {
|
|
120
|
+
type: String,
|
|
121
|
+
required: true,
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* If the browser can't support webp we fallback to this image format.
|
|
125
|
+
* `jpg, png, webp`
|
|
126
|
+
* */
|
|
127
|
+
fallbackFormat: {
|
|
128
|
+
type: String,
|
|
129
|
+
default: 'jpg',
|
|
130
|
+
validator(value) {
|
|
131
|
+
// The value must match one of these strings
|
|
132
|
+
return ['jpg', 'png', 'gif'].indexOf(value) !== -1;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* 1x width of the image. Width or height must be defined. Ideally both.
|
|
137
|
+
* */
|
|
138
|
+
width: {
|
|
139
|
+
type: [String, Number],
|
|
140
|
+
default: null,
|
|
141
|
+
},
|
|
142
|
+
/**
|
|
143
|
+
* 1x height of the image. Width or height must be defined. Ideally both.
|
|
144
|
+
* */
|
|
145
|
+
height: {
|
|
146
|
+
type: [String, Number],
|
|
147
|
+
default: null,
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* Alt text for the image
|
|
151
|
+
* */
|
|
152
|
+
alt: {
|
|
153
|
+
type: String,
|
|
154
|
+
default: '',
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Loading hint to the browser - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading
|
|
158
|
+
* `lazy, eager`
|
|
159
|
+
* */
|
|
160
|
+
loading: {
|
|
161
|
+
type: String,
|
|
162
|
+
default: null,
|
|
163
|
+
validator(value) {
|
|
164
|
+
// The value must match one of these strings
|
|
165
|
+
return ['lazy', 'eager'].indexOf(value) !== -1;
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Focus area when modifying images. Focus area has no effect on the default 'scale' fit.
|
|
170
|
+
* https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/specify-focus-area
|
|
171
|
+
* */
|
|
172
|
+
focus: {
|
|
173
|
+
type: String,
|
|
174
|
+
default: 'center',
|
|
175
|
+
validator(value) {
|
|
176
|
+
// The value must match one of these strings
|
|
177
|
+
// eslint-disable-next-line max-len
|
|
178
|
+
return ['center', 'top', 'right', 'left', 'bottom', 'top_right', 'top_left', 'bottom_right', 'bottom_left', 'face', 'faces'].indexOf(value) !== -1;
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Focus area when modifying images. Focus area has no effect on the default 'scale' fit.
|
|
183
|
+
* https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/change-the-resizing-behavior
|
|
184
|
+
* */
|
|
185
|
+
fit: {
|
|
186
|
+
type: String,
|
|
187
|
+
default: 'scale',
|
|
188
|
+
validator(value) {
|
|
189
|
+
// The value must match one of these strings
|
|
190
|
+
return ['pad', 'fill', 'scale', 'crop', 'thumb'].indexOf(value) !== -1;
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
/**
|
|
194
|
+
* Sources sizes.
|
|
195
|
+
* Array of objects for different image sources.
|
|
196
|
+
* Sample object:
|
|
197
|
+
* {
|
|
198
|
+
width: 1440,
|
|
199
|
+
height: 620,
|
|
200
|
+
media: 'min-width: 1025px',
|
|
201
|
+
url: '//some-protocol-relative-contentful-url'
|
|
202
|
+
}
|
|
203
|
+
* */
|
|
204
|
+
sourceSizes: {
|
|
205
|
+
type: Array,
|
|
206
|
+
required: false,
|
|
207
|
+
default: () => [],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
setup(props) {
|
|
211
|
+
const {
|
|
212
|
+
alt,
|
|
213
|
+
contentfulSrc,
|
|
214
|
+
width,
|
|
215
|
+
height,
|
|
216
|
+
} = toRefs(props);
|
|
217
|
+
|
|
218
|
+
const buildUrl = (image = null, multiplier = 1) => {
|
|
219
|
+
let src = image && image.url ? `${image.url}?` : `${contentfulSrc.value}?`;
|
|
220
|
+
let imgWidth = image ? image.width : width.value;
|
|
221
|
+
let imgHeight = image ? image.height : height.value;
|
|
222
|
+
// The max contentful image size is 4000px so we have to
|
|
223
|
+
// impose a limit of 2000px here for both height and width
|
|
224
|
+
// so when we request the retina x2 image we don't go over the 4000px limit
|
|
225
|
+
let newMultiplier;
|
|
226
|
+
if (imgWidth >= 2000) {
|
|
227
|
+
newMultiplier = imgWidth / 1999;
|
|
228
|
+
imgWidth = 1999;
|
|
229
|
+
imgHeight = Math.round(imgHeight / newMultiplier);
|
|
230
|
+
}
|
|
231
|
+
if (imgHeight >= 2000) {
|
|
232
|
+
newMultiplier = imgHeight / 1999;
|
|
233
|
+
imgHeight = 1999;
|
|
234
|
+
imgWidth = Math.round(imgWidth / newMultiplier);
|
|
235
|
+
}
|
|
236
|
+
if (imgWidth) {
|
|
237
|
+
src += `w=${imgWidth * multiplier}`;
|
|
238
|
+
}
|
|
239
|
+
if (imgWidth && imgHeight) {
|
|
240
|
+
src += '&';
|
|
241
|
+
}
|
|
242
|
+
if (imgHeight) {
|
|
243
|
+
src += `h=${imgHeight * multiplier}`;
|
|
244
|
+
}
|
|
245
|
+
return src;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const setQuality = (imgWidth, imgScale) => {
|
|
249
|
+
if (imgScale === '2x') {
|
|
250
|
+
return 65;
|
|
251
|
+
}
|
|
252
|
+
// Smaller images show a marked degradation at 80 quality so we bump it up to 95
|
|
253
|
+
if (imgWidth && parseInt(imgWidth, 10) < 200) {
|
|
254
|
+
return 95;
|
|
255
|
+
}
|
|
256
|
+
return 80;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const caption = computed(() => {
|
|
260
|
+
if (alt.value && alt.value.charAt(0) === '^') {
|
|
261
|
+
return alt.value.slice(1).trim();
|
|
262
|
+
}
|
|
263
|
+
return '';
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
buildUrl,
|
|
268
|
+
caption,
|
|
269
|
+
setQuality,
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span v-if="timeLeft">
|
|
3
|
+
{{ remainingHours }}h {{ timeLeft.minutes() }}m {{ timeLeft.seconds() }}s
|
|
4
|
+
</span>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import {
|
|
9
|
+
ref,
|
|
10
|
+
toRefs,
|
|
11
|
+
onBeforeUnmount,
|
|
12
|
+
onMounted,
|
|
13
|
+
computed,
|
|
14
|
+
} from 'vue-demi';
|
|
15
|
+
import moment from 'moment';
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
props: {
|
|
19
|
+
msLeft: {
|
|
20
|
+
type: Number,
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
setup(props) {
|
|
25
|
+
const { msLeft } = toRefs(props);
|
|
26
|
+
|
|
27
|
+
const interval = ref(null);
|
|
28
|
+
const timeLeft = ref(null);
|
|
29
|
+
|
|
30
|
+
const remainingHours = computed(() => {
|
|
31
|
+
return Math.floor(timeLeft.value.asHours());
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
timeLeft.value = moment.duration(msLeft.value > 0 ? msLeft.value : 0, 'milliseconds');
|
|
36
|
+
|
|
37
|
+
if (timeLeft.value > 0) {
|
|
38
|
+
const countdownInterval = 1000;
|
|
39
|
+
|
|
40
|
+
interval.value = setInterval(() => {
|
|
41
|
+
timeLeft.value = moment.duration(timeLeft.value - countdownInterval, 'milliseconds');
|
|
42
|
+
|
|
43
|
+
if (timeLeft.value <= 0) {
|
|
44
|
+
clearInterval(interval.value);
|
|
45
|
+
}
|
|
46
|
+
}, countdownInterval);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
onBeforeUnmount(() => {
|
|
51
|
+
if (interval.value) {
|
|
52
|
+
clearInterval(interval.value);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return { timeLeft, remainingHours };
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<transition
|
|
3
|
+
@enter="enter"
|
|
4
|
+
@leave="leave"
|
|
5
|
+
>
|
|
6
|
+
<slot></slot>
|
|
7
|
+
</transition>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
import {
|
|
12
|
+
toRefs,
|
|
13
|
+
} from 'vue-demi';
|
|
14
|
+
import { expand, collapse } from '../utils/expander';
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
props: {
|
|
18
|
+
property: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: 'height',
|
|
21
|
+
},
|
|
22
|
+
delay: {
|
|
23
|
+
type: Number,
|
|
24
|
+
default: 500,
|
|
25
|
+
},
|
|
26
|
+
easing: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: 'ease',
|
|
29
|
+
},
|
|
30
|
+
skipEnter: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
},
|
|
34
|
+
skipLeave: {
|
|
35
|
+
type: Boolean,
|
|
36
|
+
default: false,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
setup(props) {
|
|
40
|
+
const {
|
|
41
|
+
property,
|
|
42
|
+
delay,
|
|
43
|
+
easing,
|
|
44
|
+
skipEnter,
|
|
45
|
+
skipLeave,
|
|
46
|
+
} = toRefs(props);
|
|
47
|
+
|
|
48
|
+
const enter = (el, done) => {
|
|
49
|
+
if (skipEnter.value) {
|
|
50
|
+
return done();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
expand(el, {
|
|
54
|
+
property: property.value,
|
|
55
|
+
delay: delay.value,
|
|
56
|
+
easing: easing.value,
|
|
57
|
+
done,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const leave = (el, done) => {
|
|
64
|
+
if (skipLeave.value) {
|
|
65
|
+
return done();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
collapse(el, {
|
|
69
|
+
property: property.value,
|
|
70
|
+
delay: delay.value,
|
|
71
|
+
easing: easing.value,
|
|
72
|
+
done,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
enter,
|
|
80
|
+
leave,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
</script>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tw-whitespace-normal">
|
|
3
|
+
<button
|
|
4
|
+
class="tw-w-full tw-py-2 tw-flex tw-justify-between tw-not-prose"
|
|
5
|
+
@click="toggleFAQ"
|
|
6
|
+
>
|
|
7
|
+
<h3 class="tw-text-subhead tw-text-left">
|
|
8
|
+
{{ title }}
|
|
9
|
+
</h3>
|
|
10
|
+
<kv-material-icon
|
|
11
|
+
class="tw-w-4 tw-h-4"
|
|
12
|
+
:icon="open ? mdiChevronUp : mdiChevronDown"
|
|
13
|
+
@click.stop="toggleFAQ"
|
|
14
|
+
/>
|
|
15
|
+
</button>
|
|
16
|
+
<kv-expandable easing="ease-in-out">
|
|
17
|
+
<div v-show="open">
|
|
18
|
+
<div class="tw-prose tw-pb-4 tw-pt-2">
|
|
19
|
+
<slot></slot>
|
|
20
|
+
<!-- eslint-disable vue/no-v-html -->
|
|
21
|
+
<div
|
|
22
|
+
v-if="content !== ''"
|
|
23
|
+
v-html="content"
|
|
24
|
+
>
|
|
25
|
+
</div>
|
|
26
|
+
<!--eslint-enable-->
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</kv-expandable>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
import {
|
|
35
|
+
computed,
|
|
36
|
+
onMounted,
|
|
37
|
+
watch,
|
|
38
|
+
ref,
|
|
39
|
+
toRefs,
|
|
40
|
+
} from 'vue-demi';
|
|
41
|
+
import {
|
|
42
|
+
mdiChevronDown,
|
|
43
|
+
mdiChevronUp,
|
|
44
|
+
} from '@mdi/js';
|
|
45
|
+
import { paramCase } from 'change-case';
|
|
46
|
+
|
|
47
|
+
import KvExpandable from './KvExpandable.vue';
|
|
48
|
+
import KvMaterialIcon from './KvMaterialIcon.vue';
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
components: {
|
|
52
|
+
KvMaterialIcon,
|
|
53
|
+
KvExpandable,
|
|
54
|
+
},
|
|
55
|
+
props: {
|
|
56
|
+
/**
|
|
57
|
+
* Question Title
|
|
58
|
+
* */
|
|
59
|
+
title: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: '',
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Question Content - can accept raw html
|
|
65
|
+
* */
|
|
66
|
+
content: {
|
|
67
|
+
type: String,
|
|
68
|
+
default: '',
|
|
69
|
+
},
|
|
70
|
+
active: {
|
|
71
|
+
type: Boolean,
|
|
72
|
+
default: false,
|
|
73
|
+
},
|
|
74
|
+
routeHash: {
|
|
75
|
+
type: String,
|
|
76
|
+
default: '',
|
|
77
|
+
},
|
|
78
|
+
kvTrackFunction: {
|
|
79
|
+
type: Function,
|
|
80
|
+
default: () => {},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
emits: [
|
|
84
|
+
'toggle',
|
|
85
|
+
],
|
|
86
|
+
setup(props, { emit }) {
|
|
87
|
+
const {
|
|
88
|
+
title,
|
|
89
|
+
active,
|
|
90
|
+
} = toRefs(props);
|
|
91
|
+
const open = ref(active.value || false);
|
|
92
|
+
const titleSlugified = computed(() => paramCase(title.value));
|
|
93
|
+
|
|
94
|
+
const toggleFAQ = () => {
|
|
95
|
+
props.kvTrackFunction('faq', 'toggle', titleSlugified.value, open.value ? 'expand' : 'collapse');
|
|
96
|
+
open.value = !open.value;
|
|
97
|
+
emit('toggle', { title: titleSlugified.value });
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
watch(active, (val) => {
|
|
101
|
+
open.value = val;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
onMounted(() => {
|
|
105
|
+
/** Allows directly linking to the question via a hash equal to slugified title */
|
|
106
|
+
if (props.routeHash === `#${titleSlugified.value}`) {
|
|
107
|
+
open.value = true;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
open,
|
|
113
|
+
mdiChevronDown,
|
|
114
|
+
mdiChevronUp,
|
|
115
|
+
titleSlugified,
|
|
116
|
+
toggleFAQ,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
</script>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="country"
|
|
4
|
+
:class="`kv-flag kv-flag--${aspectRatio}`"
|
|
5
|
+
:style="{ maxWidth: spriteWidth, minWidth: spriteWidth }"
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
class="
|
|
9
|
+
kv-flag__wrapper
|
|
10
|
+
kv-flag-svg
|
|
11
|
+
tw-bg-gray-100
|
|
12
|
+
tw-relative
|
|
13
|
+
tw-overflow-hidden
|
|
14
|
+
tw-h-0
|
|
15
|
+
tw-w-full
|
|
16
|
+
tw-border
|
|
17
|
+
tw-border-gray-600
|
|
18
|
+
!tw-bg-cover
|
|
19
|
+
fib
|
|
20
|
+
"
|
|
21
|
+
:class="classes"
|
|
22
|
+
>
|
|
23
|
+
<span class="tw-sr-only">{{ countryName }}</span>
|
|
24
|
+
</div>
|
|
25
|
+
<span
|
|
26
|
+
v-if="showName"
|
|
27
|
+
class="tw-text-h4 tw-my-2"
|
|
28
|
+
>{{ getNameByCode(country) }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
export default {
|
|
34
|
+
name: 'KvFlag',
|
|
35
|
+
props: {
|
|
36
|
+
/**
|
|
37
|
+
* 2 letter ISO country code of the flag to show
|
|
38
|
+
* */
|
|
39
|
+
country: {
|
|
40
|
+
type: String,
|
|
41
|
+
required: true,
|
|
42
|
+
},
|
|
43
|
+
/**
|
|
44
|
+
* Aspect Ratio of the flag image
|
|
45
|
+
* `4x3, 1x1`
|
|
46
|
+
* */
|
|
47
|
+
aspectRatio: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: '4x3',
|
|
50
|
+
},
|
|
51
|
+
/**
|
|
52
|
+
* Show the name of the country next to the flag
|
|
53
|
+
*/
|
|
54
|
+
showName: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
default: false,
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* Override the width of the flag
|
|
60
|
+
*/
|
|
61
|
+
widthOverride: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: null,
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* Hide the border around the flag
|
|
67
|
+
*/
|
|
68
|
+
hideBorder: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: false,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
async setup() {
|
|
74
|
+
const countryList = await import('~/node_modules/flag-icons/country.json');
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
countryList,
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
computed: {
|
|
81
|
+
spriteWidth() {
|
|
82
|
+
if (this.widthOverride) {
|
|
83
|
+
return this.widthOverride;
|
|
84
|
+
}
|
|
85
|
+
return '100%';
|
|
86
|
+
},
|
|
87
|
+
countryName() {
|
|
88
|
+
return `Flag of ${this.getNameByCode(this.country)}`;
|
|
89
|
+
},
|
|
90
|
+
classes() {
|
|
91
|
+
return {
|
|
92
|
+
[`fi-${this.country.toLowerCase()}`]: true,
|
|
93
|
+
'tw-border-0': this.hideBorder,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
methods: {
|
|
98
|
+
getNameByCode(code) {
|
|
99
|
+
const uppercaseCode = code?.toLowerCase() ?? '';
|
|
100
|
+
return this.countryList?.default?.find((country) => country.code === uppercaseCode)?.name ?? '';
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<style lang="postcss" scoped>
|
|
107
|
+
@import "~/node_modules/flag-icons/css/flag-icons.min.css";
|
|
108
|
+
|
|
109
|
+
.kv-flag__wrapper {
|
|
110
|
+
line-height: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.kv-flag--4x3 .kv-flag__wrapper.kv-flag-svg {
|
|
114
|
+
padding-bottom: 71%;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.kv-flag--1x1 .kv-flag__wrapper.kv-flag-svg {
|
|
118
|
+
padding-bottom: 96%;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
class="tw-grid tw-gap-2 md:tw-gap-3 lg:tw-gap-3.5"
|
|
5
|
+
>
|
|
6
|
+
<slot></slot>
|
|
7
|
+
</component>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
/**
|
|
12
|
+
* A light wrapper around Tailwind's grid class, setting the gaps as specified by design.
|
|
13
|
+
* To use, specify the number of columns as a tailwind class on this component.
|
|
14
|
+
* e.g., `<kv-grid class="tw-grid-cols-6 md:tw-grid-cols-12"> grid children here... </kv-grid>`
|
|
15
|
+
*/
|
|
16
|
+
export default {
|
|
17
|
+
props: {
|
|
18
|
+
/**
|
|
19
|
+
* Element name of the grid container element.
|
|
20
|
+
* e.g., <kv-grid as="ul"><li>list item... renders <ul><li>list item...
|
|
21
|
+
* */
|
|
22
|
+
as: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: 'div',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
</script>
|