Skip to content

Commit 5325fed

Browse files
committed
feat(tabs): use dynamic transition based on navigated tab
1 parent ebbf51b commit 5325fed

File tree

7 files changed

+38
-20
lines changed

7 files changed

+38
-20
lines changed

docs/components/demos/tabs/DemoTabsWithViews.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ const tabs = [
2424
<ATabs
2525
class="a-tabs-bordered"
2626
:tabs="tabs"
27-
transition="view-next"
2827
>
2928
<!-- 👉 Account -->
3029
<template #account>

docs/guide/components/tabs.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ If you are passing array of object that doesn't has `value` property to the `tab
105105
:::tip DX Improved 🚀
106106
Comparing other frameworks, You don't have to write a separate component for each tab. You can just use dynamic slot to render the tab content.
107107
:::
108+
109+
:::details Dynamic Transition
110+
If you don't specify any transition `ATabs` component will assign dynamic transition based on visited tab. For example, if you visit any tab next to the active tab, `view-next` transition will trigger or else `view-previous`.
111+
112+
If you want to use custom transition, you can use `transition` prop to override the dynamic transition.
113+
:::
108114
::::
109115

110116
:::::

packages/anu-vue/src/components/tabs/ATabs.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ const { options, select: selectTab, value: activeTab } = useSelection({
7070
provide(ActiveViewSymbol, activeTab)
7171
provide(ATabBindingsSymbol, refTabs)
7272
73+
// 👉 Tabs dynamic transition
74+
const tabsDynamicTransition = ref<'view-next' | 'view-previous'>()
75+
const activeTabIndex = ref(-1)
76+
7377
// Flag to check if tabs are overflowed (For showing arrows)
7478
const areTabsOverflowed = ref<boolean>()
7579
const shouldShowArrows = computed(() => !props.vertical && areTabsOverflowed.value)
@@ -150,6 +154,15 @@ function handleTabClick(tab: ATabProps | string, index: number) {
150154
const { trigger: triggerActiveTabWatcher } = watchTriggerable(activeTab, val => {
151155
const index = options.value.findIndex(option => option.value === val)
152156
157+
const previousActiveTabIndex = activeTabIndex.value
158+
activeTabIndex.value = index
159+
160+
// ℹ️ Calculate dynamic transition for tabs
161+
if (activeTabIndex.value > previousActiveTabIndex)
162+
tabsDynamicTransition.value = 'view-next'
163+
else
164+
tabsDynamicTransition.value = 'view-previous'
165+
153166
// Set active tab ref to set active indicator styles
154167
refActiveTab.value = refTabs.value[index]
155168
@@ -336,7 +349,7 @@ const handleTabsContentSwipe = useDebounceFn((direction: UseSwipeDirection) => {
336349
<!-- 👉 Slot: Default => For rendering `AViews` -->
337350
<AViews
338351
v-model="activeTab"
339-
:transition="transition"
352+
:transition="props.transition === undefined ? tabsDynamicTransition : props.transition"
340353
@swipe="handleTabsContentSwipe"
341354
>
342355
<AView

packages/anu-vue/src/components/tabs/meta.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import type { LiteralUnion } from 'type-fest'
21
import type { ExtractPublicPropTypes } from 'vue'
32
import type { ATabProps } from '@/components/tab'
4-
import type { Transitions } from '@/transitions'
3+
import { transition as transitionProp } from '@/composables/useProps'
54

65
// ℹ️ Make sure to checkout meta definition rules
76

@@ -46,10 +45,7 @@ export const aTabsProps = {
4645
/**
4746
* Change tab transition
4847
*/
49-
transition: {
50-
type: String as PropType<LiteralUnion<Transitions, string>>,
51-
default: 'view-next',
52-
},
48+
transition: transitionProp,
5349
} as const
5450
export type ATabsProps = ExtractPublicPropTypes<typeof aTabsProps>
5551

packages/anu-vue/src/components/views/AViews.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts" setup>
22
import type { VNode } from 'vue'
3-
import { h } from 'vue'
3+
import { TransitionGroup, h } from 'vue'
44
import type { AViewsEvents } from './meta'
55
import { aViewsProps } from './meta'
66
import { ActiveViewSymbol, ViewGroupModel } from './symbol'
@@ -58,11 +58,11 @@ watch(direction, value => {
5858
:class="defaultsClass"
5959
:style="defaultsStyle"
6060
>
61-
<TransitionGroup
62-
tag="div"
63-
:class="`${props.transition}-group`"
61+
<Component
62+
:is="props.transition ? TransitionGroup : 'div'"
63+
:class="props.transition && `${props.transition}-group`"
6464
class="a-views-wrapper relative"
65-
:name="props.transition"
65+
v-bind="props.transition && { tag: 'div', name: props.transition || undefined }"
6666
>
6767
<slot>
6868
<template
@@ -74,6 +74,6 @@ watch(direction, value => {
7474
</slot>
7575
</template>
7676
</slot>
77-
</TransitionGroup>
77+
</Component>
7878
</div>
7979
</template>

packages/anu-vue/src/components/views/meta.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { UseSwipeDirection } from '@vueuse/core'
2-
import type { LiteralUnion } from 'type-fest'
32
import type { ExtractPublicPropTypes } from 'vue'
4-
import type { Transitions } from '@/transitions'
3+
import { transition as transitionProp } from '@/composables/useProps'
54

65
// ℹ️ Make sure to checkout meta definition rules
76

@@ -19,7 +18,7 @@ export const aViewsProps = {
1918
* Transition to use
2019
*/
2120
transition: {
22-
type: String as PropType<LiteralUnion<Transitions, string>>,
21+
...transitionProp,
2322
default: 'fade',
2423
},
2524
} as const

packages/anu-vue/src/composables/useProps.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { createDefu } from 'defu'
2-
import type { LiteralUnion } from 'type-fest'
3-
import type { PropType } from 'vue'
41
import type { ConfigurableValue } from '@/composables/useConfigurable'
52
import type { ThemeColors } from '@/plugin'
3+
import type { Transitions } from '@/transitions'
64
import type { NamedColors } from '@/utils/color'
5+
import { createDefu } from 'defu'
6+
import type { LiteralUnion } from 'type-fest'
7+
import type { PropType } from 'vue'
78

89
export type ColorProp = LiteralUnion<ThemeColors | NamedColors, string> | undefined
910

@@ -19,6 +20,10 @@ export const configurable = {
1920
type: [Array, String, Number, Object, undefined] as PropType<ConfigurableValue>,
2021
} as const
2122

23+
export const transition = {
24+
type: [String, null] as PropType<LiteralUnion<Transitions, string> | null>,
25+
} as const
26+
2227
export const defuProps = createDefu((obj, key, value) => {
2328
// If merging type => Just override the existing type
2429
if (key === 'type') {

0 commit comments

Comments
 (0)