Skip to content

Commit becd5af

Browse files
authored
feat(ktabs): add before change prop (#2644)
* feat(ktabs): add before change prop * fix: minor fix * fix: address pr feedback
1 parent ccd61fd commit becd5af

File tree

4 files changed

+161
-4
lines changed

4 files changed

+161
-4
lines changed

docs/components/tabs.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,80 @@ const tabChange = (hash: string): void => {
221221
</script>
222222
```
223223

224+
### beforeChange
225+
226+
Prop that takes a function return value of which determines whether tab change should be skipped. Useful when some information needs to me brought to user's attention before they leave the content in the currently displayed tab.
227+
228+
The function receives new tab value as an argument and returns a boolean value, `false` will prevent tab change. Defaults to `() => true`.
229+
230+
<KTabs
231+
:before-change="onBeforeTabChange"
232+
:model-value="confirmedTab"
233+
:tabs="tabs"
234+
>
235+
<template #tab1>
236+
Tab 1 content
237+
</template>
238+
<template #tab2>
239+
Tab 2 content
240+
</template>
241+
</KTabs>
242+
243+
<KPrompt
244+
message="Notice that the tab doesn't change until you've confirmed your action."
245+
:visible="confirmPromptVisible"
246+
@cancel="onCancel"
247+
@proceed="onConfirm"
248+
/>
249+
250+
```vue
251+
<template>
252+
<KTabs
253+
:before-change="onBeforeTabChange"
254+
:model-value="activeTab"
255+
:tabs="tabs"
256+
>
257+
<template #tab1>
258+
Tab 1 content
259+
</template>
260+
<template #tab2>
261+
Tab 2 content
262+
</template>
263+
</KTabs>
264+
265+
<KPrompt
266+
message="Notice that the tab doesn't change until you've confirmed your action."
267+
:visible="confirmPromptVisible"
268+
@cancel="onCancel"
269+
@proceed="onConfirm"
270+
/>
271+
</template>
272+
273+
<script setup lang="ts">
274+
const activeTab = ref<string>('#tab1')
275+
const confirmPromptVisible = ref<boolean>(false)
276+
const targetTab = ref<string | null>(null)
277+
278+
const onBeforeTabChange = async (tab: string) => {
279+
confirmPromptVisible.value = true
280+
targetTab.value = tab
281+
282+
return false
283+
}
284+
285+
const onConfirm = () => {
286+
confirmPromptVisible.value = false
287+
activeTab.value = targetTab.value!
288+
targetTab.value = null
289+
}
290+
291+
const onCancel = () => {
292+
confirmPromptVisible.value = false
293+
targetTab.value = null
294+
}
295+
</script>
296+
```
297+
224298
## Slots
225299

226300
### anchor & panel
@@ -351,6 +425,25 @@ const panelsActiveHash = ref('#gateway')
351425
const panelsChange = (hash: string) => {
352426
panelsActiveHash.value = hash;
353427
}
428+
429+
const confirmedTab = ref<string>('#tab1')
430+
const confirmPromptVisible = ref<boolean>(false)
431+
const targetTab = ref<string | null>(null)
432+
const onBeforeTabChange = async (tab: string) => {
433+
confirmPromptVisible.value = true
434+
targetTab.value = tab
435+
436+
return false
437+
}
438+
const onConfirm = () => {
439+
confirmPromptVisible.value = false
440+
confirmedTab.value = targetTab.value!
441+
targetTab.value = null
442+
}
443+
const onCancel = () => {
444+
confirmPromptVisible.value = false
445+
targetTab.value = null
446+
}
354447
</script>
355448

356449
<style lang="scss" scoped>

sandbox/pages/SandboxTabs.vue

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@
6666
Tab 3 content
6767
</div>
6868
</SandboxSectionComponent>
69+
<SandboxSectionComponent title="beforeChange">
70+
<KTabs
71+
:before-change="onBeforeTabChange"
72+
:model-value="confirmedTab"
73+
:tabs="items"
74+
>
75+
<template #tab1>
76+
Tab 1 content
77+
</template>
78+
<template #tab2>
79+
Tab 2 content
80+
</template>
81+
<template #tab3>
82+
Tab 3 content
83+
</template>
84+
</KTabs>
85+
86+
<KPrompt
87+
message="Notice that the tab doesn't change until you've confirmed your action."
88+
:visible="confirmPromptVisible"
89+
@cancel="onCancel"
90+
@proceed="onConfirm"
91+
/>
92+
</SandboxSectionComponent>
6993

7094
<!-- Slots -->
7195
<SandboxTitleComponent
@@ -155,6 +179,7 @@ const items = [
155179
156180
const vModel1 = ref<string>('#tab2')
157181
const vModel2 = ref<string>('#tab2')
182+
const confirmedTab = ref<string>('#tab1')
158183
159184
const dynamicRouterViewItems = [
160185
{
@@ -168,4 +193,22 @@ const dynamicRouterViewItems = [
168193
to: { hash: '#two' },
169194
},
170195
]
196+
197+
const confirmPromptVisible = ref<boolean>(false)
198+
const targetTab = ref<string | null>(null)
199+
const onBeforeTabChange = async (tab: string) => {
200+
confirmPromptVisible.value = true
201+
targetTab.value = tab
202+
203+
return false
204+
}
205+
const onConfirm = () => {
206+
confirmPromptVisible.value = false
207+
confirmedTab.value = targetTab.value!
208+
targetTab.value = null
209+
}
210+
const onCancel = () => {
211+
confirmPromptVisible.value = false
212+
targetTab.value = null
213+
}
171214
</script>

src/components/KTabs/KTabs.cy.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,19 @@ describe('KTabs', () => {
126126
cy.get('.tab-item .tab-link').eq(1).should('not.have.attr', 'href')
127127
})
128128

129+
it('does not change the tab when beforeChange returns false', () => {
130+
cy.mount(KTabs, {
131+
props: {
132+
tabs: TABS,
133+
beforeChange: () => false,
134+
},
135+
})
136+
137+
cy.get('.tab-item').eq(1).click().then(() => {
138+
cy.wrap(Cypress.vueWrapper.emitted()).should('not.have.property', 'change')
139+
})
140+
})
141+
129142
describe('slots', () => {
130143
it('provides the #hash slot content', () => {
131144
const picturesSlot = 'I love pictures'

src/components/KTabs/KTabs.vue

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ const props = defineProps({
8787
default: 0,
8888
validator: (val: number): boolean => val >= -1 && val <= 32767,
8989
},
90+
// async function
91+
beforeChange: {
92+
type: Function as PropType<(tab: string) => Promise<boolean>>,
93+
default: () => true,
94+
},
9095
})
9196
9297
const emit = defineEmits<{
@@ -96,10 +101,13 @@ const emit = defineEmits<{
96101
97102
const activeTab = ref<string>(props.modelValue ? props.modelValue : props.tabs[0]?.hash)
98103
99-
const handleTabChange = (tab: string): void => {
100-
activeTab.value = tab
101-
emit('change', tab)
102-
emit('update:modelValue', tab)
104+
const handleTabChange = async (tab: string): Promise<void> => {
105+
// if beforeChange is not a function, skip the check
106+
if (typeof props.beforeChange !== 'function' || await props.beforeChange(tab)) {
107+
activeTab.value = tab
108+
emit('change', tab)
109+
emit('update:modelValue', tab)
110+
}
103111
}
104112
105113
const getTabSlotName = (tabHash: string): string => tabHash.replace('#', '')

0 commit comments

Comments
 (0)