@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.
Files changed (177) hide show
  1. package/CHANGELOG.md +11 -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 +7 -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,143 @@
1
+ <template>
2
+ <div
3
+ :class="classes"
4
+ :style="styles"
5
+ >
6
+ <label
7
+ class="tw-inline-flex tw-gap-2 tw-items-center tw-relative hover:tw-cursor-pointer"
8
+ :class="{ 'tw-opacity-low': disabled }"
9
+ :for="uuid"
10
+ >
11
+ <input
12
+ :id="uuid"
13
+ v-bind="inputAttrs"
14
+ ref="switchRef"
15
+ class="tw-sr-only tw-peer"
16
+ type="checkbox"
17
+ role="switch"
18
+ :checked="modelValue"
19
+ :disabled="disabled"
20
+ v-on="inputListeners"
21
+ @change.prevent="onChange"
22
+ >
23
+ <!-- switch background -->
24
+ <div
25
+ class="
26
+ tw-w-7 tw-h-4 tw-rounded-full tw-relative tw-overflow-hidden
27
+ peer-focus-visible:tw-ring-2 peer-focus-visible:tw-ring-action
28
+ tw-bg-tertiary peer-checked:tw-bg-action
29
+ tw-transition-all tw-ease-in-out
30
+ "
31
+ >
32
+ </div>
33
+ <!-- switch inner circle -->
34
+ <div
35
+ class="
36
+ tw-flex-shrink-0 tw-w-3 tw-h-3
37
+ tw-absolute tw-m-0.5 tw-top-0
38
+ tw-rounded-full
39
+ tw-bg-white
40
+ tw-transform tw-transition-all tw-ease-in-out
41
+ peer-checked:tw-translate-x-3
42
+ "
43
+ ></div>
44
+ <!-- label -->
45
+ <div class="tw-flex-1 peer-focus-visible:tw-ring-2 peer-focus-visible:tw-ring-action">
46
+ <slot></slot>
47
+ </div>
48
+ </label>
49
+ </div>
50
+ </template>
51
+
52
+ <script>
53
+ import {
54
+ ref,
55
+ onMounted,
56
+ } from 'vue-demi';
57
+ import { nanoid } from 'nanoid';
58
+ import { useAttrs } from '../utils/attrs';
59
+
60
+ const emits = [
61
+ 'update:modelValue',
62
+ ];
63
+
64
+ /**
65
+ * KvSwitch
66
+ * A visual treatment of a checkbox element for handling on/off states in UI.
67
+ *
68
+ * A11y considerations:
69
+ *
70
+ * - https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Switch_role
71
+ *
72
+ * Prior art:
73
+ *
74
+ * - https://scottaohara.github.io/a11y_styled_form_controls/src/checkbox--switch/
75
+ * - https://adrianroselli.com/2019/08/under-engineered-toggles-too.html
76
+ * - https://headlessui.dev/react/switch#labels
77
+ * - https://react-spectrum.adobe.com/react-spectrum/Switch.html
78
+ *
79
+ */
80
+ export default {
81
+ inheritAttrs: false,
82
+ // v-model will change when checked value changes
83
+ model: {
84
+ prop: 'modelValue',
85
+ event: 'update:modelValue',
86
+ },
87
+ props: {
88
+ /**
89
+ * Whether the switch is on or off
90
+ * */
91
+ modelValue: {
92
+ type: Boolean,
93
+ default: false,
94
+ },
95
+ /**
96
+ * Prevents the switch from being toggled or focused
97
+ * */
98
+ disabled: {
99
+ type: Boolean,
100
+ default: false,
101
+ },
102
+ },
103
+ emits,
104
+ setup(props, context) {
105
+ const { emit } = context;
106
+ const uuid = ref(`kvs-${nanoid(10)}`);
107
+ const switchRef = ref(null);
108
+
109
+ const {
110
+ classes,
111
+ styles,
112
+ inputAttrs,
113
+ inputListeners,
114
+ } = useAttrs(context, emits);
115
+
116
+ const onChange = (event) => {
117
+ emit('update:modelValue', event.target.checked);
118
+ };
119
+
120
+ const focus = () => {
121
+ switchRef.value.focus();
122
+ };
123
+ const blur = () => {
124
+ switchRef.value.blur();
125
+ };
126
+
127
+ onMounted(() => {
128
+ uuid.value = `kvs-${nanoid(10)}`;
129
+ });
130
+
131
+ return {
132
+ uuid,
133
+ onChange,
134
+ focus,
135
+ blur,
136
+ classes,
137
+ styles,
138
+ inputAttrs,
139
+ inputListeners,
140
+ };
141
+ },
142
+ };
143
+ </script>
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <button
3
+ :id="`kv-tab-${forPanel}`"
4
+ class="tw-text-h3 tw-mb-1.5 tw-whitespace-nowrap tw-text-left"
5
+ :class="{ 'hover:tw-text-action-highlight' : !isActive,
6
+ 'md:tw-border-l-2 tw-border-transparent md:tw-pl-2' : vertical,
7
+ 'tw-text-action-highlight' : isActive && vertical
8
+ }"
9
+ role="tab"
10
+ :aria-selected="isActive"
11
+ :aria-controls="`kv-tab-panel-${forPanel}`"
12
+ :tabindex="isActive ? null : -1"
13
+ @click="handleTabClicked"
14
+ >
15
+ <slot></slot>
16
+ </button>
17
+ </template>
18
+
19
+ <script>
20
+ import {
21
+ toRefs,
22
+ inject,
23
+ computed,
24
+ onMounted,
25
+ getCurrentInstance,
26
+ } from 'vue-demi';
27
+
28
+ export default {
29
+ props: {
30
+ /**
31
+ * A unique id which correspondes to an `id` property on the KvTabPanel it controls
32
+ * e.g., <kv-tab for="foo">... <kv-tab-panel id="foo">
33
+ * */
34
+ forPanel: {
35
+ type: String,
36
+ required: true,
37
+ },
38
+ /**
39
+ * The tab should be initially selected.
40
+ * */
41
+ selected: {
42
+ type: Boolean,
43
+ default: false,
44
+ },
45
+ vertical: {
46
+ type: Boolean,
47
+ default: false,
48
+ },
49
+ },
50
+ setup(props) {
51
+ const {
52
+ forPanel,
53
+ } = toRefs(props);
54
+
55
+ const kvTabContext = inject('$KvTabContext');
56
+
57
+ const isActive = computed(() => {
58
+ let navItems = [];
59
+ let selectedIndex = 0;
60
+ if (kvTabContext) {
61
+ navItems = kvTabContext.navItems;
62
+ selectedIndex = kvTabContext.selectedIndex;
63
+ }
64
+ return navItems[selectedIndex]?.forPanel === forPanel.value;
65
+ });
66
+
67
+ const index = computed(() => {
68
+ let navItems = [];
69
+ if (kvTabContext) {
70
+ navItems = kvTabContext.navItems;
71
+ }
72
+ return navItems?.findIndex((navItem) => navItem.forPanel === forPanel.value);
73
+ });
74
+
75
+ const handleTabClicked = () => {
76
+ kvTabContext.setTab(index.value);
77
+ };
78
+
79
+ onMounted(() => {
80
+ const instance = getCurrentInstance();
81
+ kvTabContext.navItems.push(instance.proxy);
82
+ });
83
+
84
+ return {
85
+ isActive,
86
+ handleTabClicked,
87
+ };
88
+ },
89
+ };
90
+ </script>
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <transition
3
+ enter-active-class="tw-transition-opacity tw-duration-700"
4
+ leave-active-class=""
5
+ enter-class="tw-opacity-0 tw-absolute tw-top-0"
6
+ enter-to-class="tw-opacity-full"
7
+ leave-class=""
8
+ leave-to-class="tw-opacity-0 tw-absolute tw-top-0"
9
+ >
10
+ <div
11
+ v-show="isActive"
12
+ :id="`kv-tab-panel-${id}`"
13
+ :key="`kv-tab-panel-${id}`"
14
+ role="tabpanel"
15
+ :aria-hidden="!isActive"
16
+ :aria-labelledby="`kv-tab-${id}`"
17
+ tabindex="0"
18
+ >
19
+ <slot></slot>
20
+ </div>
21
+ </transition>
22
+ </template>
23
+
24
+ <script>
25
+ import {
26
+ computed,
27
+ toRefs,
28
+ inject,
29
+ } from 'vue-demi';
30
+
31
+ export default {
32
+ props: {
33
+ /**
34
+ * A unique id which correspondes to a `for` property on the KvTab which controls it
35
+ * e.g., <kv-tab for="foo">... <kv-tab-panel id="foo">
36
+ * */
37
+ id: {
38
+ type: String,
39
+ required: true,
40
+ },
41
+ },
42
+ setup(props) {
43
+ const {
44
+ id,
45
+ } = toRefs(props);
46
+
47
+ const kvTabContext = inject('$KvTabContext');
48
+
49
+ const isActive = computed(() => {
50
+ let navItems = [];
51
+ let selectedIndex = 0;
52
+ if (kvTabContext) {
53
+ navItems = kvTabContext.navItems;
54
+ selectedIndex = kvTabContext.selectedIndex;
55
+ }
56
+ return navItems[selectedIndex]?.forPanel === id.value;
57
+ });
58
+
59
+ return {
60
+ isActive,
61
+ };
62
+ },
63
+ };
64
+ </script>
@@ -0,0 +1,182 @@
1
+ <template>
2
+ <div :class="vertical ? 'md:tw-flex' : ''">
3
+ <div
4
+ role="tablist"
5
+ class="
6
+ tw-flex tw-overflow-auto tw-relative
7
+ tw-gap-x-2.5 md:tw-gap-x-5 lg:tw-gap-x-6
8
+ tw-mb-3 lg:tw-mb-4
9
+ "
10
+ :class="{'md:tw-flex-col md:tw-mr-3' : vertical}"
11
+ @keydown="handleKeyDown($event)"
12
+ >
13
+ <!-- @slot Tab Navigation -->
14
+ <slot
15
+ name="tabNav"
16
+ ></slot>
17
+
18
+ <!-- indicator bar -->
19
+ <div
20
+ class="
21
+ tw-absolute tw-bottom-0 tw-h-0.5 tw-left-0
22
+ tw-bg-primary-inverse tw-rounded-full
23
+ tw-origin-left tw-transition-all tw-duration-300
24
+ "
25
+ :class="{ 'tw-hidden md:tw-block tw-top-0 md:tw-bg-action-highlight' : vertical}"
26
+ :style="`
27
+ width: ${selectedTabEl && !vertical ? selectedTabEl.clientWidth : 3}px;
28
+ height: ${selectedTabEl && vertical ? `${selectedTabEl.clientHeight}px` : '0.25rem'};
29
+ transform: ${selectedTabEl && !vertical ? `translateX(${selectedTabEl.offsetLeft}px)`
30
+ : selectedTabEl ? `translateY(${selectedTabEl.offsetTop}px)` : null};
31
+ `"
32
+ ></div>
33
+ </div>
34
+ <div class="tw-relative">
35
+ <!-- @slot Tab Panels -->
36
+ <slot name="tabPanels"></slot>
37
+ </div>
38
+ </div>
39
+ </template>
40
+
41
+ <script>
42
+ /**
43
+ * Components for implementing a tab UI pattern. Requires KvTab and KvTabPanel components.
44
+ *
45
+ * Keyboard navigation and aria roles + attributes handled according to
46
+ * https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-1/tabs.html
47
+ *
48
+ * Usage:
49
+ *
50
+ * ```
51
+ * <kv-tabs>
52
+ * <template #tabNav>
53
+ * <kv-tab for-panel="foo">Foo</kv-tab>
54
+ * <kv-tab for-panel="bar">Bar</kv-tab>
55
+ * </template>
56
+ * <template #tabPanels>
57
+ * <kv-tab-panel id="foo">Foo content</kv-tab-panel>
58
+ * <kv-tab-panel id="bar">Bar content</kv-tab-panel>
59
+ * </template>
60
+ * </kv-tabs>
61
+ * ```
62
+ */
63
+ import {
64
+ ref,
65
+ reactive,
66
+ provide,
67
+ computed,
68
+ onMounted,
69
+ getCurrentInstance,
70
+ onBeforeUnmount,
71
+ } from 'vue-demi';
72
+
73
+ export default {
74
+ props: {
75
+ vertical: {
76
+ type: Boolean,
77
+ default: false,
78
+ },
79
+ },
80
+ setup(props, { emit }) {
81
+ const tabContext = reactive({
82
+ selectedIndex: 0,
83
+ setTab: null,
84
+ navItems: [],
85
+ });
86
+ const selectedTabResizeObserver = ref(null);
87
+
88
+ const selectedTabEl = computed(() => {
89
+ const { navItems, selectedIndex } = tabContext;
90
+ return navItems[selectedIndex]?.$el ?? null;
91
+ });
92
+
93
+ const forceUpdate = () => {
94
+ const instance = getCurrentInstance();
95
+ if (instance) {
96
+ instance.proxy.$forceUpdate();
97
+ }
98
+ };
99
+
100
+ const setTab = (index) => {
101
+ tabContext.selectedIndex = index;
102
+ selectedTabEl.value.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
103
+
104
+ /**
105
+ * Triggers when the selected tab changes
106
+ *
107
+ * @property {number} index Index of the newly selected tab
108
+ */
109
+ emit('tab-changed', index);
110
+ };
111
+
112
+ tabContext.setTab = setTab; // setTab definition in tab context
113
+ provide('$KvTabContext', tabContext);
114
+
115
+ const handleKeyDown = (event) => {
116
+ const { navItems, selectedIndex } = tabContext;
117
+
118
+ const focusActiveTab = () => {
119
+ const activeTab = navItems
120
+ .find((navItem) => navItem.isActive);
121
+ if (activeTab) {
122
+ activeTab.$el?.focus();
123
+ }
124
+ };
125
+ const count = navItems.length;
126
+
127
+ if (event.key === 'ArrowRight') {
128
+ event.preventDefault();
129
+ const nextIndex = (selectedIndex + 1) % count;
130
+ setTab(nextIndex);
131
+ focusActiveTab();
132
+ }
133
+
134
+ if (event.key === 'ArrowLeft') {
135
+ event.preventDefault();
136
+ const prevIndex = (selectedIndex - 1 + count) % count;
137
+ setTab(prevIndex);
138
+ focusActiveTab();
139
+ }
140
+
141
+ if (event.key === 'Home') {
142
+ event.preventDefault();
143
+ setTab(0);
144
+ focusActiveTab();
145
+ }
146
+
147
+ if (event.key === 'End') {
148
+ event.preventDefault();
149
+ setTab(count - 1);
150
+ focusActiveTab();
151
+ }
152
+ };
153
+
154
+ onMounted(() => {
155
+ // check if any of the KvTab components are declaratively selected
156
+ tabContext.navItems.forEach((navItem, index) => {
157
+ if (navItem.selected) {
158
+ setTab(index);
159
+ }
160
+ });
161
+
162
+ // Tab size can change as @font-face fonts come in or
163
+ // the screen breakpoint changes the font size. If this happens
164
+ // we need to re-size and position the indicator bar.
165
+ selectedTabResizeObserver.value = new ResizeObserver(() => {
166
+ forceUpdate();
167
+ });
168
+ selectedTabResizeObserver.value.observe(selectedTabEl.value);
169
+ });
170
+
171
+ onBeforeUnmount(() => {
172
+ selectedTabResizeObserver.value.disconnect();
173
+ });
174
+
175
+ return {
176
+ handleKeyDown,
177
+ selectedTabEl,
178
+ tabContext,
179
+ };
180
+ },
181
+ };
182
+ </script>
@@ -0,0 +1,247 @@
1
+ <template>
2
+ <div
3
+ class="tw-inline-flex"
4
+ :class="classes"
5
+ :style="styles"
6
+ >
7
+ <div
8
+ class="tw-relative tw-w-full "
9
+ :class="{ 'tw-opacity-low': disabled }"
10
+ >
11
+ <!-- eslint-disable max-len -->
12
+ <input
13
+ :id="id"
14
+ ref="textInputRef"
15
+ :type="type"
16
+ class="
17
+ tw-h-6 tw-w-full
18
+ tw-px-2
19
+ tw-border tw-border-tertiary
20
+ tw-rounded-sm
21
+ tw-appearance-none
22
+ tw-text-base
23
+ tw-bg-primary
24
+ tw-ring-inset
25
+ focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-action focus:tw-border-transparent
26
+ "
27
+ :class="{
28
+ 'tw-pr-6 tw-bg-danger tw-border-danger-highlight tw-bg-opacity-low focus:tw-ring-danger-highlight'
29
+ : !valid,
30
+ 'tw-bg-tertiary'
31
+ : disabled,
32
+ 'tw-pl-6' : icon,
33
+ }"
34
+ :placeholder="placeholder"
35
+ :disabled="disabled"
36
+ v-bind="inputAttrs"
37
+ :value="modelValue"
38
+ v-on="inputListeners"
39
+ @input="onInput"
40
+ >
41
+ <!-- eslint-enable max-len -->
42
+ <kv-material-icon
43
+ v-if="icon"
44
+ :icon="icon"
45
+ class="tw-w-3 tw-h-3 tw-absolute tw-top-1.5 tw-left-1.5 tw-pointer-events-none"
46
+ />
47
+ <kv-material-icon
48
+ v-if="!valid"
49
+ :icon="mdiAlertCircleOutline"
50
+ class="tw-w-3 tw-h-3 tw-absolute tw-top-1.5 tw-right-1.5
51
+ tw-pointer-events-none tw-text-danger"
52
+ />
53
+ <button
54
+ v-show="canClear && valid && !!inputText"
55
+ type="button"
56
+ class="tw-absolute tw-top-1.5 tw-right-1.5"
57
+ @click="clearInput"
58
+ >
59
+ <span class="tw-sr-only">clear input</span>
60
+ <kv-material-icon
61
+ class="tw-w-3 tw-h-3"
62
+ :icon="mdiClose"
63
+ />
64
+ </button>
65
+ <div
66
+ v-if="$slots.error"
67
+ class="tw-text-danger tw-text-small tw-font-medium tw-mt-1"
68
+ >
69
+ <!-- @slot Used in conjuction with the `valid` prop to tell the user what must be fixed -->
70
+ <slot name="error"></slot>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </template>
75
+
76
+ <script>
77
+ import {
78
+ ref,
79
+ toRefs,
80
+ } from 'vue-demi';
81
+ import { mdiAlertCircleOutline, mdiClose } from '@mdi/js';
82
+ import KvMaterialIcon from './KvMaterialIcon.vue';
83
+ import { useAttrs } from '../utils/attrs';
84
+
85
+ const emits = [
86
+ 'input',
87
+ 'update:modelValue',
88
+ ];
89
+ /* eslint-disable max-len */
90
+
91
+ /**
92
+ * Use as you would an `<input type="text" />`. Ensure you're using a label element for a11y.
93
+ *
94
+ * @usage
95
+ * ```
96
+ * <label for="input_id">Street Address</label>
97
+ * <kv-text-input id="input_id" v-model="streetAddress" />
98
+ *
99
+ * or
100
+ *
101
+ * <label for="input_id">Street Address</label>
102
+ * <kv-text-input id="input_id" :value="streetAddress" @input="(val) => streetAddress = val" />
103
+ * ```
104
+ */
105
+ /* eslint-enable max-len */
106
+
107
+ export default {
108
+ components: {
109
+ KvMaterialIcon,
110
+ },
111
+ inheritAttrs: false,
112
+ // v-model will update when a different value is input
113
+ model: {
114
+ prop: 'modelValue',
115
+ event: 'update:modelValue',
116
+ },
117
+ props: {
118
+ /**
119
+ * Id of the input, used to associate the input with a <label> element.
120
+ * */
121
+ id: {
122
+ type: String,
123
+ required: true,
124
+ },
125
+ /**
126
+ * Value of the text in the input. Used if you're not using the standard v-model bindings
127
+ * ```
128
+ * <kv-text-input :value="streetAddress" @input="(val) => streetAddress = val" />
129
+ * ```
130
+ * */
131
+ modelValue: {
132
+ type: [String, Number, Boolean],
133
+ default: '',
134
+ },
135
+ /**
136
+ * Text that appears in the form control when it has no value set
137
+ * Should not be used as a label, but rather an example.
138
+ * Bad: Enter an address Good: 1234 E Cherry Lane
139
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefplaceholder
140
+ * */
141
+ placeholder: {
142
+ type: String,
143
+ default: null,
144
+ },
145
+ /**
146
+ * Prevents the input from being edited or focused
147
+ * */
148
+ disabled: {
149
+ type: Boolean,
150
+ default: false,
151
+ },
152
+ /**
153
+ * When set to false, visually indicates to the user that the contents of the input need
154
+ * to be changed
155
+ * */
156
+ valid: {
157
+ type: Boolean,
158
+ default: true,
159
+ },
160
+ /*
161
+ * SVG path data passed in from an imported @mdi/js module.
162
+ * Displayed on the left hand side of the input
163
+ * ```
164
+ * import { mdiMagnify } from '@mdi/js';
165
+ * data() { return { mdiMagnify } };
166
+ * <kv-text-input :icon="mdiMagnify" />`
167
+ * ```
168
+ * */
169
+ icon: {
170
+ type: String,
171
+ default: null,
172
+ },
173
+ /**
174
+ * Can be used to set show special keyboards for mobile users
175
+ * E.g., type="number" or type="tel"
176
+ * */
177
+ type: {
178
+ type: String,
179
+ default: 'text',
180
+ },
181
+ /**
182
+ * When set to true, adds a button positioned to the right edge of the input containing an “X”
183
+ * */
184
+ canClear: {
185
+ type: Boolean,
186
+ default: false,
187
+ },
188
+ },
189
+ emits,
190
+ setup(props, context) {
191
+ const { emit } = context;
192
+ const {
193
+ modelValue,
194
+ } = toRefs(props);
195
+
196
+ const textInputRef = ref(null);
197
+ const inputText = ref(modelValue.value);
198
+
199
+ const {
200
+ classes,
201
+ styles,
202
+ inputAttrs,
203
+ inputListeners,
204
+ } = useAttrs(context, emits);
205
+
206
+ const onInput = (event) => {
207
+ /**
208
+ * The value that is currently in the input
209
+ * @event input
210
+ * @type {Event}
211
+ */
212
+ inputText.value = event.target.value;
213
+ emit('input', event.target.value);
214
+ emit('update:modelValue', event.target.value);
215
+ };
216
+
217
+ const focus = () => {
218
+ textInputRef.value.focus();
219
+ };
220
+
221
+ const blur = () => {
222
+ textInputRef.value.blur();
223
+ };
224
+
225
+ const clearInput = () => {
226
+ inputText.value = '';
227
+ emit('input', '');
228
+ emit('update:modelValue', '');
229
+ };
230
+
231
+ return {
232
+ mdiAlertCircleOutline,
233
+ mdiClose,
234
+ onInput,
235
+ focus,
236
+ blur,
237
+ clearInput,
238
+ inputText,
239
+ classes,
240
+ styles,
241
+ inputAttrs,
242
+ inputListeners,
243
+ textInputRef,
244
+ };
245
+ },
246
+ };
247
+ </script>