@kiva/kv-components 2.0.0 → 3.0.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/{.eslintrc.js → .eslintrc.cjs} +1 -1
- package/CHANGELOG.md +45 -0
- package/__mocks__/ResizeObserver.js +13 -0
- package/package.json +23 -10
- package/{postcss.config.js → postcss.config.cjs} +2 -2
- package/{tailwind.config.js → tailwind.config.cjs} +3 -3
- package/tests/unit/jest-setup.js +5 -0
- package/tests/unit/specs/components/KvButton.spec.js +14 -25
- package/tests/unit/specs/components/KvCarousel.spec.js +11 -0
- package/tests/unit/specs/components/KvCheckbox.spec.js +73 -14
- package/tests/unit/specs/components/KvLightbox.spec.js +14 -0
- package/tests/unit/specs/components/KvProgressBar.spec.js +11 -0
- package/tests/unit/specs/components/KvRadio.spec.js +94 -5
- package/tests/unit/specs/components/KvSelect.spec.js +113 -0
- package/tests/unit/specs/components/KvSwitch.spec.js +92 -33
- package/tests/unit/specs/components/KvTabPanel.spec.js +11 -0
- package/tests/unit/specs/components/KvTabs.spec.js +99 -0
- package/tests/unit/specs/components/KvTextInput.spec.js +86 -9
- package/tests/unit/specs/components/KvTextLink.spec.js +16 -24
- package/tests/unit/specs/components/KvToast.spec.js +11 -0
- package/tests/unit/utils/addVueRouter.js +24 -0
- package/utils/attrs.js +62 -0
- package/utils/{themeUtils.js → themeUtils.cjs} +0 -0
- package/vue/.storybook/{main.js → main.cjs} +13 -5
- package/vue/.storybook/preview.js +6 -1
- package/vue/KvButton.vue +80 -53
- package/vue/KvCarousel.vue +142 -106
- package/vue/KvCheckbox.vue +86 -60
- package/vue/KvContentfulImg.vue +45 -34
- package/vue/KvLightbox.vue +108 -69
- package/vue/KvProgressBar.vue +33 -19
- package/vue/KvRadio.vue +72 -41
- package/vue/KvSelect.vue +46 -20
- package/vue/KvSwitch.vue +55 -33
- package/vue/KvTab.vue +49 -21
- package/vue/KvTabPanel.vue +26 -6
- package/vue/KvTabs.vue +70 -53
- package/vue/KvTextInput.vue +71 -48
- package/vue/KvTextLink.vue +42 -20
- package/vue/KvThemeProvider.vue +1 -1
- package/vue/KvToast.vue +53 -37
- package/vue/stories/KvCheckbox.stories.js +5 -5
- package/vue/stories/KvSwitch.stories.js +2 -2
- package/vue/stories/KvTabs.stories.js +8 -8
- package/vue/stories/KvTextInput.stories.js +1 -1
- package/vue/stories/KvThemeProvider.stories.js +1 -1
- package/vue/stories/KvToast.stories.js +3 -2
- package/vue/stories/StyleguidePrimitives.stories.js +9 -9
- package/.babelrc +0 -16
- package/jest.config.js +0 -36
package/vue/KvRadio.vue
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
:class="classes"
|
|
4
|
+
:style="styles"
|
|
5
|
+
>
|
|
3
6
|
<label
|
|
4
7
|
class="tw-inline-flex tw-items-center tw-align-middle"
|
|
5
8
|
:class="{ 'tw-opacity-low': disabled }"
|
|
6
9
|
:for="uuid"
|
|
7
10
|
>
|
|
8
11
|
<input
|
|
9
|
-
v-bind="
|
|
12
|
+
v-bind="inputAttrs"
|
|
10
13
|
:id="uuid"
|
|
11
14
|
ref="radioRef"
|
|
12
15
|
class="tw-peer tw-appearance-none tw-w-max"
|
|
@@ -50,6 +53,18 @@
|
|
|
50
53
|
|
|
51
54
|
<script>
|
|
52
55
|
import { nanoid } from 'nanoid';
|
|
56
|
+
import {
|
|
57
|
+
computed,
|
|
58
|
+
onMounted,
|
|
59
|
+
ref,
|
|
60
|
+
toRefs,
|
|
61
|
+
} from 'vue-demi';
|
|
62
|
+
import { useAttrs } from '../utils/attrs';
|
|
63
|
+
|
|
64
|
+
const emits = [
|
|
65
|
+
'change',
|
|
66
|
+
'update:modelValue',
|
|
67
|
+
];
|
|
53
68
|
|
|
54
69
|
/* eslint-disable max-len */
|
|
55
70
|
|
|
@@ -79,10 +94,9 @@ import { nanoid } from 'nanoid';
|
|
|
79
94
|
|
|
80
95
|
export default {
|
|
81
96
|
inheritAttrs: false,
|
|
82
|
-
// v-model will update when the checked radio changes
|
|
83
97
|
model: {
|
|
84
|
-
prop: '
|
|
85
|
-
event: '
|
|
98
|
+
prop: 'modelValue',
|
|
99
|
+
event: 'update:modelValue',
|
|
86
100
|
},
|
|
87
101
|
props: {
|
|
88
102
|
/**
|
|
@@ -123,45 +137,62 @@ export default {
|
|
|
123
137
|
type: [String, Number, Boolean],
|
|
124
138
|
default: false,
|
|
125
139
|
},
|
|
140
|
+
modelValue: {
|
|
141
|
+
type: [String, Number, Boolean],
|
|
142
|
+
default: false,
|
|
143
|
+
},
|
|
126
144
|
},
|
|
127
|
-
|
|
145
|
+
emits,
|
|
146
|
+
setup(props, context) {
|
|
147
|
+
const { emit } = context;
|
|
148
|
+
const {
|
|
149
|
+
value,
|
|
150
|
+
checked,
|
|
151
|
+
modelValue,
|
|
152
|
+
} = toRefs(props);
|
|
153
|
+
|
|
154
|
+
let uuid = ref(`kvr-${nanoid(10)}`);
|
|
155
|
+
const radioRef = ref(null);
|
|
156
|
+
|
|
157
|
+
const {
|
|
158
|
+
classes,
|
|
159
|
+
styles,
|
|
160
|
+
inputAttrs,
|
|
161
|
+
inputListeners,
|
|
162
|
+
} = useAttrs(context, emits);
|
|
163
|
+
|
|
164
|
+
const isChecked = computed(() => {
|
|
165
|
+
if (typeof modelValue.value === typeof value.value) {
|
|
166
|
+
return modelValue.value === value.value;
|
|
167
|
+
}
|
|
168
|
+
return Boolean(checked.value);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
onMounted(() => {
|
|
172
|
+
uuid = `kvr-${nanoid(10)}`;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const onChange = (event) => {
|
|
176
|
+
emit('change', event.target.value);
|
|
177
|
+
emit('update:modelValue', event.target.value);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const focus = () => radioRef.focus();
|
|
181
|
+
|
|
182
|
+
const blur = () => radioRef.blur();
|
|
183
|
+
|
|
128
184
|
return {
|
|
129
|
-
uuid
|
|
185
|
+
uuid,
|
|
186
|
+
isChecked,
|
|
187
|
+
radioRef,
|
|
188
|
+
onChange,
|
|
189
|
+
focus,
|
|
190
|
+
blur,
|
|
191
|
+
classes,
|
|
192
|
+
styles,
|
|
193
|
+
inputAttrs,
|
|
194
|
+
inputListeners,
|
|
130
195
|
};
|
|
131
196
|
},
|
|
132
|
-
computed: {
|
|
133
|
-
isChecked() {
|
|
134
|
-
if (typeof this.checked === typeof this.value) {
|
|
135
|
-
return this.checked === this.value;
|
|
136
|
-
}
|
|
137
|
-
return Boolean(this.checked);
|
|
138
|
-
},
|
|
139
|
-
inputListeners() {
|
|
140
|
-
return {
|
|
141
|
-
// Pass through any listeners from the parent to the input element, like blur, focus, etc.
|
|
142
|
-
// https://vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components
|
|
143
|
-
...this.$listeners,
|
|
144
|
-
// ...except for the listener to the 'change' event which is emitted by this component
|
|
145
|
-
change: () => {},
|
|
146
|
-
};
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
mounted() {
|
|
150
|
-
this.uuid = `kvr-${nanoid(10)}`;
|
|
151
|
-
},
|
|
152
|
-
methods: {
|
|
153
|
-
onChange(event) {
|
|
154
|
-
/**
|
|
155
|
-
* triggers when the value changes
|
|
156
|
-
*/
|
|
157
|
-
this.$emit('change', event.target.value);
|
|
158
|
-
},
|
|
159
|
-
focus() {
|
|
160
|
-
this.$refs.radioRef.focus();
|
|
161
|
-
},
|
|
162
|
-
blur() {
|
|
163
|
-
this.$refs.radioRef.blur();
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
197
|
};
|
|
167
198
|
</script>
|
package/vue/KvSelect.vue
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="tw-inline-flex"
|
|
4
|
+
:class="classes"
|
|
5
|
+
:style="styles"
|
|
6
|
+
>
|
|
3
7
|
<div class="tw-relative tw-w-full">
|
|
4
8
|
<!-- eslint-disable max-len -->
|
|
5
9
|
<select
|
|
6
10
|
:id="id"
|
|
11
|
+
v-bind="inputAttrs"
|
|
7
12
|
:disabled="disabled"
|
|
8
|
-
:value="
|
|
13
|
+
:value="modelValue"
|
|
9
14
|
class="tw-text-base tw-bg-primary tw-h-6 tw-pr-4 tw-pl-2 tw-border tw-border-tertiary tw-rounded-sm tw-appearance-none tw-w-full tw-ring-inset focus:tw-outline-none focus:tw-ring-2 focus:tw-ring-action focus:tw-border-transparent"
|
|
10
15
|
:class="{ 'tw-opacity-low': disabled }"
|
|
11
16
|
@change="onChange"
|
|
@@ -23,8 +28,15 @@
|
|
|
23
28
|
</template>
|
|
24
29
|
|
|
25
30
|
<script>
|
|
31
|
+
import 'vue-demi';
|
|
26
32
|
import { mdiChevronDown } from '@mdi/js';
|
|
27
33
|
import KvMaterialIcon from './KvMaterialIcon.vue';
|
|
34
|
+
import { useAttrs } from '../utils/attrs';
|
|
35
|
+
|
|
36
|
+
const emits = [
|
|
37
|
+
'change',
|
|
38
|
+
'update:modelValue',
|
|
39
|
+
];
|
|
28
40
|
|
|
29
41
|
export default {
|
|
30
42
|
components: {
|
|
@@ -32,8 +44,8 @@ export default {
|
|
|
32
44
|
},
|
|
33
45
|
// v-model will change when select value updates
|
|
34
46
|
model: {
|
|
35
|
-
prop: '
|
|
36
|
-
event: '
|
|
47
|
+
prop: 'modelValue',
|
|
48
|
+
event: 'update:modelValue',
|
|
37
49
|
},
|
|
38
50
|
props: {
|
|
39
51
|
/**
|
|
@@ -44,13 +56,6 @@ export default {
|
|
|
44
56
|
required: true,
|
|
45
57
|
default: '',
|
|
46
58
|
},
|
|
47
|
-
/**
|
|
48
|
-
* Initial selected value
|
|
49
|
-
* */
|
|
50
|
-
value: {
|
|
51
|
-
type: [Number, String],
|
|
52
|
-
default: 0,
|
|
53
|
-
},
|
|
54
59
|
/**
|
|
55
60
|
* Use if select is disabled
|
|
56
61
|
* */
|
|
@@ -58,21 +63,42 @@ export default {
|
|
|
58
63
|
type: Boolean,
|
|
59
64
|
default: false,
|
|
60
65
|
},
|
|
66
|
+
/**
|
|
67
|
+
* modelValue prop
|
|
68
|
+
* */
|
|
69
|
+
modelValue: {
|
|
70
|
+
type: [Number, String],
|
|
71
|
+
default: 0,
|
|
72
|
+
},
|
|
61
73
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
emits,
|
|
75
|
+
setup(props, context) {
|
|
76
|
+
const { emit } = context;
|
|
77
|
+
|
|
78
|
+
const {
|
|
79
|
+
classes,
|
|
80
|
+
styles,
|
|
81
|
+
inputAttrs,
|
|
82
|
+
inputListeners,
|
|
83
|
+
} = useAttrs(context, emits);
|
|
84
|
+
|
|
85
|
+
const onChange = (event) => {
|
|
69
86
|
/**
|
|
70
87
|
* The value that the select has changed to
|
|
71
88
|
* @event change
|
|
72
89
|
* @type {Event}
|
|
73
90
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
emit('change', event.target.value);
|
|
92
|
+
emit('update:modelValue', event.target.value);
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
mdiChevronDown,
|
|
96
|
+
onChange,
|
|
97
|
+
classes,
|
|
98
|
+
styles,
|
|
99
|
+
inputAttrs,
|
|
100
|
+
inputListeners,
|
|
101
|
+
};
|
|
76
102
|
},
|
|
77
103
|
};
|
|
78
104
|
</script>
|
package/vue/KvSwitch.vue
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
:class="classes"
|
|
4
|
+
:style="styles"
|
|
5
|
+
>
|
|
3
6
|
<label
|
|
4
|
-
class="tw-inline-flex tw-gap-2 tw-items-center tw-relative"
|
|
7
|
+
class="tw-inline-flex tw-gap-2 tw-items-center tw-relative hover:tw-cursor-pointer"
|
|
5
8
|
:class="{ 'tw-opacity-low': disabled }"
|
|
6
9
|
:for="uuid"
|
|
7
10
|
>
|
|
8
11
|
<input
|
|
9
12
|
:id="uuid"
|
|
10
|
-
v-bind="
|
|
13
|
+
v-bind="inputAttrs"
|
|
11
14
|
ref="switchRef"
|
|
12
15
|
class="tw-sr-only tw-peer"
|
|
13
16
|
type="checkbox"
|
|
14
17
|
role="switch"
|
|
18
|
+
:checked="modelValue"
|
|
15
19
|
:disabled="disabled"
|
|
16
20
|
v-on="inputListeners"
|
|
17
21
|
@change.prevent="onChange"
|
|
@@ -46,7 +50,16 @@
|
|
|
46
50
|
</template>
|
|
47
51
|
|
|
48
52
|
<script>
|
|
53
|
+
import {
|
|
54
|
+
ref,
|
|
55
|
+
onMounted,
|
|
56
|
+
} from 'vue-demi';
|
|
49
57
|
import { nanoid } from 'nanoid';
|
|
58
|
+
import { useAttrs } from '../utils/attrs';
|
|
59
|
+
|
|
60
|
+
const emits = [
|
|
61
|
+
'update:modelValue',
|
|
62
|
+
];
|
|
50
63
|
|
|
51
64
|
/**
|
|
52
65
|
* KvSwitch
|
|
@@ -68,14 +81,14 @@ export default {
|
|
|
68
81
|
inheritAttrs: false,
|
|
69
82
|
// v-model will change when checked value changes
|
|
70
83
|
model: {
|
|
71
|
-
prop: '
|
|
72
|
-
event: '
|
|
84
|
+
prop: 'modelValue',
|
|
85
|
+
event: 'update:modelValue',
|
|
73
86
|
},
|
|
74
87
|
props: {
|
|
75
88
|
/**
|
|
76
89
|
* Whether the switch is on or off
|
|
77
90
|
* */
|
|
78
|
-
|
|
91
|
+
modelValue: {
|
|
79
92
|
type: Boolean,
|
|
80
93
|
default: false,
|
|
81
94
|
},
|
|
@@ -87,35 +100,44 @@ export default {
|
|
|
87
100
|
default: false,
|
|
88
101
|
},
|
|
89
102
|
},
|
|
90
|
-
|
|
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
|
+
|
|
91
131
|
return {
|
|
92
|
-
uuid
|
|
132
|
+
uuid,
|
|
133
|
+
onChange,
|
|
134
|
+
focus,
|
|
135
|
+
blur,
|
|
136
|
+
classes,
|
|
137
|
+
styles,
|
|
138
|
+
inputAttrs,
|
|
139
|
+
inputListeners,
|
|
93
140
|
};
|
|
94
141
|
},
|
|
95
|
-
computed: {
|
|
96
|
-
inputListeners() {
|
|
97
|
-
return {
|
|
98
|
-
// Pass through any listeners from the parent to the input element, like blur, focus, etc.
|
|
99
|
-
// https://vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components
|
|
100
|
-
...this.$listeners,
|
|
101
|
-
// ...except for the listener to the 'change' event which is emitted by this component
|
|
102
|
-
change: () => {},
|
|
103
|
-
};
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
mounted() {
|
|
107
|
-
this.uuid = `kvs-${nanoid(10)}`;
|
|
108
|
-
},
|
|
109
|
-
methods: {
|
|
110
|
-
onChange(event) {
|
|
111
|
-
this.$emit('change', event.target.checked);
|
|
112
|
-
},
|
|
113
|
-
focus() {
|
|
114
|
-
this.$refs.switchRef.focus();
|
|
115
|
-
},
|
|
116
|
-
blur() {
|
|
117
|
-
this.$refs.switchRef.blur();
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
142
|
};
|
|
121
143
|
</script>
|
package/vue/KvTab.vue
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<button
|
|
3
|
-
:id="`kv-tab-${
|
|
3
|
+
:id="`kv-tab-${forPanel}`"
|
|
4
4
|
class="tw-text-h3 tw-mb-1.5 tw-whitespace-nowrap"
|
|
5
5
|
:class="{ 'hover:tw-text-action-highlight' : !isActive }"
|
|
6
6
|
role="tab"
|
|
7
7
|
:aria-selected="isActive"
|
|
8
|
-
:aria-controls="`kv-tab-panel-${
|
|
8
|
+
:aria-controls="`kv-tab-panel-${forPanel}`"
|
|
9
9
|
:tabindex="isActive ? null : -1"
|
|
10
10
|
@click="handleTabClicked"
|
|
11
11
|
>
|
|
@@ -14,14 +14,21 @@
|
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<script>
|
|
17
|
+
import {
|
|
18
|
+
toRefs,
|
|
19
|
+
inject,
|
|
20
|
+
computed,
|
|
21
|
+
onMounted,
|
|
22
|
+
getCurrentInstance,
|
|
23
|
+
} from 'vue-demi';
|
|
24
|
+
|
|
17
25
|
export default {
|
|
18
|
-
inject: ['$KvTabContext'],
|
|
19
26
|
props: {
|
|
20
27
|
/**
|
|
21
28
|
* A unique id which correspondes to an `id` property on the KvTabPanel it controls
|
|
22
29
|
* e.g., <kv-tab for="foo">... <kv-tab-panel id="foo">
|
|
23
30
|
* */
|
|
24
|
-
|
|
31
|
+
forPanel: {
|
|
25
32
|
type: String,
|
|
26
33
|
required: true,
|
|
27
34
|
},
|
|
@@ -33,23 +40,44 @@ export default {
|
|
|
33
40
|
default: false,
|
|
34
41
|
},
|
|
35
42
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
setup(props) {
|
|
44
|
+
const {
|
|
45
|
+
forPanel,
|
|
46
|
+
} = toRefs(props);
|
|
47
|
+
|
|
48
|
+
const kvTabContext = inject('$KvTabContext');
|
|
49
|
+
|
|
50
|
+
const isActive = computed(() => {
|
|
51
|
+
let navItems = [];
|
|
52
|
+
let selectedIndex = 0;
|
|
53
|
+
if (kvTabContext) {
|
|
54
|
+
navItems = kvTabContext.navItems;
|
|
55
|
+
selectedIndex = kvTabContext.selectedIndex;
|
|
56
|
+
}
|
|
57
|
+
return navItems[selectedIndex]?.forPanel === forPanel.value;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const index = computed(() => {
|
|
61
|
+
let navItems = [];
|
|
62
|
+
if (kvTabContext) {
|
|
63
|
+
navItems = kvTabContext.navItems;
|
|
64
|
+
}
|
|
65
|
+
return navItems?.findIndex((navItem) => navItem.forPanel === forPanel.value);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const handleTabClicked = () => {
|
|
69
|
+
kvTabContext.setTab(index.value);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
onMounted(() => {
|
|
73
|
+
const instance = getCurrentInstance();
|
|
74
|
+
kvTabContext.navItems.push(instance.proxy);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
isActive,
|
|
79
|
+
handleTabClicked,
|
|
80
|
+
};
|
|
53
81
|
},
|
|
54
82
|
};
|
|
55
83
|
</script>
|
package/vue/KvTabPanel.vue
CHANGED
|
@@ -22,8 +22,13 @@
|
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
24
|
<script>
|
|
25
|
+
import {
|
|
26
|
+
computed,
|
|
27
|
+
toRefs,
|
|
28
|
+
inject,
|
|
29
|
+
} from 'vue-demi';
|
|
30
|
+
|
|
25
31
|
export default {
|
|
26
|
-
inject: ['$KvTabContext'],
|
|
27
32
|
props: {
|
|
28
33
|
/**
|
|
29
34
|
* A unique id which correspondes to a `for` property on the KvTab which controls it
|
|
@@ -34,11 +39,26 @@ export default {
|
|
|
34
39
|
required: true,
|
|
35
40
|
},
|
|
36
41
|
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
};
|
|
42
62
|
},
|
|
43
63
|
};
|
|
44
64
|
</script>
|
package/vue/KvTabs.vue
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
tw-gap-x-2.5 md:tw-gap-x-5 lg:tw-gap-x-6
|
|
8
8
|
tw-mb-3 lg:tw-mb-4
|
|
9
9
|
"
|
|
10
|
-
@keydown="handleKeyDown"
|
|
10
|
+
@keydown="handleKeyDown($event)"
|
|
11
11
|
>
|
|
12
12
|
<!-- @slot Tab Navigation -->
|
|
13
13
|
<slot name="tabNav"></slot>
|
|
@@ -54,63 +54,54 @@
|
|
|
54
54
|
* </kv-tabs>
|
|
55
55
|
* ```
|
|
56
56
|
*/
|
|
57
|
+
import {
|
|
58
|
+
ref,
|
|
59
|
+
reactive,
|
|
60
|
+
provide,
|
|
61
|
+
computed,
|
|
62
|
+
onMounted,
|
|
63
|
+
getCurrentInstance,
|
|
64
|
+
onBeforeUnmount,
|
|
65
|
+
} from 'vue-demi';
|
|
66
|
+
|
|
57
67
|
export default {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
},
|
|
65
|
-
data() {
|
|
66
|
-
return {
|
|
67
|
-
tabContext: {
|
|
68
|
-
selectedIndex: 0,
|
|
69
|
-
setTab: this.setTab,
|
|
70
|
-
navItems: [], // populated by KvTab
|
|
71
|
-
},
|
|
72
|
-
selectedTabResizeObserver: null,
|
|
73
|
-
};
|
|
74
|
-
},
|
|
75
|
-
computed: {
|
|
76
|
-
selectedTabEl() {
|
|
77
|
-
const { navItems, selectedIndex } = this.tabContext;
|
|
78
|
-
return navItems[selectedIndex]?.$el ?? null;
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
mounted() {
|
|
82
|
-
// check if any of the KvTab components are declaratively selected
|
|
83
|
-
this.tabContext.navItems.forEach((navItem, index) => {
|
|
84
|
-
if (navItem.selected) {
|
|
85
|
-
this.setTab(index);
|
|
86
|
-
}
|
|
68
|
+
setup(props, { emit }) {
|
|
69
|
+
const tabContext = reactive({
|
|
70
|
+
selectedIndex: 0,
|
|
71
|
+
setTab: null,
|
|
72
|
+
navItems: [],
|
|
87
73
|
});
|
|
74
|
+
const selectedTabResizeObserver = ref(null);
|
|
88
75
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.selectedTabResizeObserver = new ResizeObserver(() => {
|
|
93
|
-
this.$forceUpdate();
|
|
76
|
+
const selectedTabEl = computed(() => {
|
|
77
|
+
const { navItems, selectedIndex } = tabContext;
|
|
78
|
+
return navItems[selectedIndex]?.$el ?? null;
|
|
94
79
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
80
|
+
|
|
81
|
+
const forceUpdate = () => {
|
|
82
|
+
const instance = getCurrentInstance();
|
|
83
|
+
if (instance) {
|
|
84
|
+
instance.proxy.$forceUpdate();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const setTab = (index) => {
|
|
89
|
+
tabContext.selectedIndex = index;
|
|
90
|
+
selectedTabEl.value.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
104
91
|
|
|
105
92
|
/**
|
|
106
93
|
* Triggers when the selected tab changes
|
|
107
94
|
*
|
|
108
95
|
* @property {number} index Index of the newly selected tab
|
|
109
96
|
*/
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
97
|
+
emit('tab-changed', index);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
tabContext.setTab = setTab; // setTab definition in tab context
|
|
101
|
+
provide('$KvTabContext', tabContext);
|
|
102
|
+
|
|
103
|
+
const handleKeyDown = (event) => {
|
|
104
|
+
const { navItems, selectedIndex } = tabContext;
|
|
114
105
|
|
|
115
106
|
const focusActiveTab = () => {
|
|
116
107
|
const activeTab = navItems
|
|
@@ -124,29 +115,55 @@ export default {
|
|
|
124
115
|
if (event.key === 'ArrowRight') {
|
|
125
116
|
event.preventDefault();
|
|
126
117
|
const nextIndex = (selectedIndex + 1) % count;
|
|
127
|
-
|
|
118
|
+
setTab(nextIndex);
|
|
128
119
|
focusActiveTab();
|
|
129
120
|
}
|
|
130
121
|
|
|
131
122
|
if (event.key === 'ArrowLeft') {
|
|
132
123
|
event.preventDefault();
|
|
133
124
|
const prevIndex = (selectedIndex - 1 + count) % count;
|
|
134
|
-
|
|
125
|
+
setTab(prevIndex);
|
|
135
126
|
focusActiveTab();
|
|
136
127
|
}
|
|
137
128
|
|
|
138
129
|
if (event.key === 'Home') {
|
|
139
130
|
event.preventDefault();
|
|
140
|
-
|
|
131
|
+
setTab(0);
|
|
141
132
|
focusActiveTab();
|
|
142
133
|
}
|
|
143
134
|
|
|
144
135
|
if (event.key === 'End') {
|
|
145
136
|
event.preventDefault();
|
|
146
|
-
|
|
137
|
+
setTab(count - 1);
|
|
147
138
|
focusActiveTab();
|
|
148
139
|
}
|
|
149
|
-
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
onMounted(() => {
|
|
143
|
+
// check if any of the KvTab components are declaratively selected
|
|
144
|
+
tabContext.navItems.forEach((navItem, index) => {
|
|
145
|
+
if (navItem.selected) {
|
|
146
|
+
setTab(index);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Tab size can change as @font-face fonts come in or
|
|
151
|
+
// the screen breakpoint changes the font size. If this happens
|
|
152
|
+
// we need to re-size and position the indicator bar.
|
|
153
|
+
selectedTabResizeObserver.value = new ResizeObserver(() => {
|
|
154
|
+
forceUpdate();
|
|
155
|
+
});
|
|
156
|
+
selectedTabResizeObserver.value.observe(selectedTabEl.value);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
onBeforeUnmount(() => {
|
|
160
|
+
selectedTabResizeObserver.value.disconnect();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
handleKeyDown,
|
|
165
|
+
selectedTabEl,
|
|
166
|
+
};
|
|
150
167
|
},
|
|
151
168
|
};
|
|
152
169
|
</script>
|