Skip to content

Commit

Permalink
fix(components/tabs): fix transition issue when switching tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
lejunyang committed Nov 4, 2024
1 parent 394fb0e commit 1217945
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 41 deletions.
5 changes: 3 additions & 2 deletions .changeset/curvy-brooms-kiss.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

## Features

- `components/config`: add `transitionRegistry` and `componentTransitions` global context config; add `useTransition` hook and `registerTransition` util
- `components/config`: add `transitionRegistry` and `transitions` global context config; add `useTransition` hook and `registerTransition` util
- `components/dialog`: add custom renderer for header, remove `title` prop
- `components/message`: support string param for `Message.open`
- `components/switch`: add `beforeUpdate` to asynchronously determine whether to update checked status
Expand All @@ -25,4 +25,5 @@
- `components/tree`: fix check methods are not working
- `components/tree`: fix wrong expose type; fix items processing issue
- `core/createCollector`: parent element can be undefined when adding item; invode getParentEl if collectOnSetup is true
- `theme/form-item`: fix vertical align issue of checkbox-group and radio-group in form-item
- `theme/form-item`: fix vertical align issue of checkbox-group and radio-group in form-item
- `components/tabs`: fix transition issue when switching tabs
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ on:

# must have permissons same as call_docs
permissions:
contents: read
pull-requests: write # for publish
contents: write
pages: write
id-token: write

Expand Down
18 changes: 3 additions & 15 deletions packages/components/src/components/config/config.context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentInternalInstance, CSSProperties, inject, provide, reactive, TransitionProps } from 'vue';
import { ComponentInternalInstance, inject, provide, reactive, TransitionProps } from 'vue';
import { iconRegistry } from '../icon/icon.registry';
import { OpenShadowComponentKey } from './config.static';
import { ThemeConfig } from 'common';
Expand All @@ -11,19 +11,7 @@ export type DynamicStyleValue =
| ((vm: ComponentInternalInstance, compName: OpenShadowComponentKey, context: any) => string | undefined)
| string;

export type ComponentTransition = TransitionProps & { moveClass?: string } & Record<
| 'enterFromStyle'
| 'enterActiveStyle'
| 'enterToStyle'
| 'appearFromStyle'
| 'appearActiveStyle'
| 'appearToStyle'
| 'leaveFromStyle'
| 'leaveActiveStyle'
| 'leaveToStyle'
| 'moveStyle',
CSSProperties
>;
export type ComponentTransition = TransitionProps & { moveClass?: string };

export const GlobalContextConfig = reactive({
lang: inBrowser && navigator.language.startsWith('zh') ? 'zh-CN' : 'en',
Expand All @@ -42,7 +30,7 @@ export const GlobalContextConfig = reactive({
scale: 1,
} as ThemeConfig,
transitionRegistry: {} as Record<string, ComponentTransition>,
componentTransitions: reduceFromComps(() => ({} as Record<string, string>), false, false),
transitions: reduceFromComps(() => ({} as Record<string, string>), false, false),
zIndex: {
teleport: 1000,
dialog: 1000,
Expand Down
10 changes: 4 additions & 6 deletions packages/components/src/components/tabs/TabItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { defineSSRCustomElement } from 'custom';
import { createDefineElement } from 'utils';
import { tabItemEmits, tabItemProps } from './type';
import { getCompParts, getTransitionProps } from 'common';
import { getCompParts } from 'common';
import { TabsCollector } from './collector';
import { useNamespace } from 'hooks';
import { useNamespace, useTransition } from 'hooks';
import { Transition } from 'vue';

const name = 'tab-item';
Expand All @@ -16,15 +16,13 @@ export const TabItem = defineSSRCustomElement({
setup(props) {
const ns = useNamespace(name);
const context = TabsCollector.child();
const getTransition = useTransition(props, name, 'panel', context?.getTransitionName);
return () => {
const { slot } = props;
const show = context?.isActive(slot as string, context?.index); // NEED whole rerender when show changes to get new transition name
// Before, this was in v-content attr. that div would be Transition's default slot after transpiled( {{ default: () => <div></div> }}). When show changed, only that div rerendered
return (
<Transition
{...getTransitionProps(props, 'panel', context?.getTransitionName?.())}
onAfterEnter={context?.transitionEnd}
>
<Transition {...getTransition()} onAfterEnter={context?.transitionEnd}>
<div class={[ns.t, ns.is('hidden', !show)]} v-content={show} part={compParts[0]}>
<slot></slot>
</div>
Expand Down
17 changes: 10 additions & 7 deletions packages/components/src/components/tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { createDefineElement } from 'utils';
import { tabsEmits, tabsProps } from './type';
import { defineIcon } from '../icon/Icon';
import { TransitionGroup, computed, nextTick, ref, watchEffect } from 'vue';
import { useNamespace } from 'hooks';
import { getCompParts, getTransitionProps } from 'common';
import { registerSwipeTransition, useNamespace, useTransition } from 'hooks';
import { getCompParts } from 'common';
import { capitalize, isArray, setStyle, toPxIfNum } from '@lun-web/utils';
import { renderCustom } from '../custom-renderer/CustomRenderer';
import { useResizeObserver, useSetupEvent } from '@lun-web/core';
Expand Down Expand Up @@ -38,11 +38,11 @@ export const Tabs = defineSSRCustomElement({
const res =
type === 'vertical'
? activeIndex > lastActiveIndex
? 'slideDown'
: 'slideUp'
? 'swipeUp'
: 'swipeDown'
: activeIndex > lastActiveIndex
? 'slideRight'
: 'slideLeft';
? 'swipeLeft'
: 'swipeRight';
return res;
};
const transitionEnd = () => (lastActiveIndex = activeIndex);
Expand Down Expand Up @@ -130,6 +130,8 @@ export const Tabs = defineSSRCustomElement({
callback: updateVar,
});

const transition = useTransition(props, name, 'panel', getTransitionName);

return () => {
const { destroyInactive, forceRender, type, noPanel } = props;
const transitionAttrs = {
Expand Down Expand Up @@ -165,7 +167,7 @@ export const Tabs = defineSSRCustomElement({
</div>
{!noPanel && (
<TransitionGroup
{...getTransitionProps(props, 'panel', getTransitionName())}
{...transition()}
{...transitionAttrs}
onAfterEnter={transitionEnd}
>
Expand Down Expand Up @@ -212,6 +214,7 @@ export const defineTabs = createDefineElement(
},
parts,
{
common: registerSwipeTransition,
icon: defineIcon,
},
);
60 changes: 54 additions & 6 deletions packages/components/src/hooks/transition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ComponentTransition, GlobalContextConfig, OpenShadowComponentKey, useContextConfig } from 'config';
import { isObject, isString } from '@lun-web/utils';
import { computed } from 'vue';
import { TransitionProps } from 'vue';
import { MaybeRefLikeOrGetter, unrefOrGet } from '@lun-web/core';

type ExtractTransitionProps<
T extends Record<string, unknown>,
Expand All @@ -10,20 +11,67 @@ type ExtractTransitionProps<
export function useTransition<
P extends Record<string, any>,
T extends ExtractTransitionProps<P> = ExtractTransitionProps<P>,
>(props: P, compName: OpenShadowComponentKey, transitionProp: T, defaultTransition?: string) {
>(props: P, compName: OpenShadowComponentKey, transitionProp: T, defaultTransition?: MaybeRefLikeOrGetter<string>) {
const config = useContextConfig();
return computed(() => {
return () => {
const val = props[transitionProp];
const registry = config.transitionRegistry,
transitionOfConfig = config.componentTransitions[compName][transitionProp];
transitionOfConfig = config.transitions[compName][transitionProp];
let defaultT: string | undefined | null;
if (isString(val) && registry[val]) return registry[val];
else if (isObject(val)) return val;
else if (registry[transitionOfConfig]) return registry[transitionOfConfig];
else if (defaultTransition && registry[defaultTransition]) return registry[defaultTransition];
});
else if ((defaultT = unrefOrGet(defaultTransition)) && registry[defaultT]) return registry[defaultT];
};
}

export function registerTransition(name: string, transition: ComponentTransition, override = false) {
if (!GlobalContextConfig.transitionRegistry[name] || override)
GlobalContextConfig.transitionRegistry[name] = transition;
}

const getAnimationHandlers = (
enterKeyframes: Keyframe[],
enterOptions: KeyframeAnimationOptions,
leaveKeyframes: Keyframe[],
leaveOptions: KeyframeAnimationOptions,
) =>
({
onEnter(el, done) {
el.animate(enterKeyframes, enterOptions).onfinish = done;
},
onLeave(el, done) {
el.animate(leaveKeyframes, leaveOptions).onfinish = done;
},
} as TransitionProps);

const commonOption: KeyframeAnimationOptions = { duration: 300 };
export const registerSwipeTransition = () => {
const getKeyframes = (factor = -1, axis = 'X') =>
[
{
transform: `translate${axis}(${100 * factor}%)`,
opacity: 0,
},
{
transform: `translate${axis}(0)`,
opacity: 1,
},
] as Keyframe[];
registerTransition(
'swipeRight',
getAnimationHandlers(getKeyframes(), commonOption, getKeyframes(1).reverse(), commonOption),
);
registerTransition(
'swipeLeft',
getAnimationHandlers(getKeyframes(1), commonOption, getKeyframes().reverse(), commonOption),
);
registerTransition(
'swipeUp',
getAnimationHandlers(getKeyframes(1, 'Y'), commonOption, getKeyframes(-1, 'Y').reverse(), commonOption),
);
registerTransition(
'swipeDown',
getAnimationHandlers(getKeyframes(-1, 'Y'), commonOption, getKeyframes(1, 'Y').reverse(), commonOption),
);
};
5 changes: 5 additions & 0 deletions packages/theme/src/scss/components/tab-item/basic.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@
overflow: hidden; // if the hidden panel is longer then current active tab-item panel, in chrome it will show scrollbar(maybe bug, as it's normal in firefox)
}
}

// for animation, need to show the hidden element while animating
.v-leave-active {
z-index: 1;
}
4 changes: 0 additions & 4 deletions src/docs/components/tabs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ title: Tabs 标签组
lang: zh-CN
---

:::warning FIXME
切换时的过渡有问题
:::

<CompThemePanel comp="tabs" :other="{ noPanel: true, items: [{label: 'Tab 1'}, {label: 'Tab 2'}] }" />

## 基本使用
Expand Down

0 comments on commit 1217945

Please sign in to comment.