refactor: backtop

pull/6213/head
tangjinzhou 2023-01-26 10:13:10 +08:00
parent 6c735fee67
commit e64a19a05a
9 changed files with 159 additions and 105 deletions

View File

@ -42,3 +42,7 @@ export const withInstall = <T>(comp: T) => {
}; };
export type MaybeRef<T> = T | Ref<T>; export type MaybeRef<T> = T | Ref<T>;
export function eventType<T>() {
return { type: [Function, Array] as PropType<T | T[]> };
}

View File

@ -17,7 +17,7 @@ You can customize the style of the button, just note the size limit: no more tha
<template> <template>
<div id="components-back-top-demo-custom"> <div id="components-back-top-demo-custom">
<a-back-top> <a-back-top @click="handleClick">
<div class="ant-back-top-inner">UP</div> <div class="ant-back-top-inner">UP</div>
</a-back-top> </a-back-top>
Scroll down to see the bottom-right Scroll down to see the bottom-right
@ -25,10 +25,15 @@ You can customize the style of the button, just note the size limit: no more tha
button. button.
</div> </div>
</template> </template>
<script setup>
const handleClick = () => {
console.log('click');
};
</script>
<style scoped> <style scoped>
#components-back-top-demo-custom .ant-back-top { :deep(#components-back-top-demo-custom) .ant-back-top {
bottom: 100px; inset-block-end: 100px;
} }
#components-back-top-demo-custom .ant-back-top-inner { #components-back-top-demo-custom .ant-back-top-inner {
height: 40px; height: 40px;

View File

@ -11,21 +11,21 @@ import {
onDeactivated, onDeactivated,
} from 'vue'; } from 'vue';
import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined'; import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined';
import addEventListener from '../vc-util/Dom/addEventListener';
import getScroll from '../_util/getScroll'; import getScroll from '../_util/getScroll';
import { getTransitionProps, Transition } from '../_util/transition'; import { getTransitionProps, Transition } from '../_util/transition';
import scrollTo from '../_util/scrollTo'; import scrollTo from '../_util/scrollTo';
import { withInstall } from '../_util/type'; import { withInstall, eventType } from '../_util/type';
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame'; import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
import useConfigInject from '../_util/hooks/useConfigInject'; import useConfigInject from '../_util/hooks/useConfigInject';
import type { MouseEventHandler } from '../_util/EventInterface'; import type { MouseEventHandler } from '../_util/EventInterface';
import useStyle from './style';
export const backTopProps = () => ({ export const backTopProps = () => ({
visibilityHeight: { type: Number, default: 400 }, visibilityHeight: { type: Number, default: 400 },
duration: { type: Number, default: 450 }, duration: { type: Number, default: 450 },
target: Function as PropType<() => HTMLElement | Window | Document>, target: Function as PropType<() => HTMLElement | Window | Document>,
prefixCls: String, prefixCls: String,
onClick: Function as PropType<MouseEventHandler>, onClick: eventType<MouseEventHandler>(),
// visible: { type: Boolean, default: undefined }, // Only for test. Don't use it. // visible: { type: Boolean, default: undefined }, // Only for test. Don't use it.
}); });
@ -39,6 +39,7 @@ const BackTop = defineComponent({
// emits: ['click'], // emits: ['click'],
setup(props, { slots, attrs, emit }) { setup(props, { slots, attrs, emit }) {
const { prefixCls, direction } = useConfigInject('back-top', props); const { prefixCls, direction } = useConfigInject('back-top', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const domRef = ref(); const domRef = ref();
const state = reactive({ const state = reactive({
visible: false, visible: false,
@ -60,26 +61,23 @@ const BackTop = defineComponent({
const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => { const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => {
const { visibilityHeight } = props; const { visibilityHeight } = props;
const scrollTop = getScroll(e.target, true); const scrollTop = getScroll(e.target, true);
state.visible = scrollTop > visibilityHeight; state.visible = scrollTop >= visibilityHeight;
}); });
const bindScrollEvent = () => { const bindScrollEvent = () => {
const { target } = props; const { target } = props;
const getTarget = target || getDefaultTarget; const getTarget = target || getDefaultTarget;
const container = getTarget(); const container = getTarget();
state.scrollEvent = addEventListener(container, 'scroll', (e: Event) => { handleScroll({ target: container });
handleScroll(e); container?.addEventListener('scroll', handleScroll);
});
handleScroll({
target: container,
});
}; };
const scrollRemove = () => { const scrollRemove = () => {
if (state.scrollEvent) { const { target } = props;
state.scrollEvent.remove(); const getTarget = target || getDefaultTarget;
} const container = getTarget();
(handleScroll as any).cancel(); handleScroll.cancel();
container?.removeEventListener('scroll', handleScroll);
}; };
watch( watch(
@ -124,6 +122,7 @@ const BackTop = defineComponent({
...attrs, ...attrs,
onClick: scrollToTop, onClick: scrollToTop,
class: { class: {
[hashId.value]: true,
[`${prefixCls.value}`]: true, [`${prefixCls.value}`]: true,
[`${attrs.class}`]: attrs.class, [`${attrs.class}`]: attrs.class,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
@ -131,12 +130,12 @@ const BackTop = defineComponent({
}; };
const transitionProps = getTransitionProps('fade'); const transitionProps = getTransitionProps('fade');
return ( return wrapSSR(
<Transition {...transitionProps}> <Transition {...transitionProps}>
<div v-show={state.visible} {...divProps} ref={domRef}> <div v-show={state.visible} {...divProps} ref={domRef}>
{slots.default?.() || defaultElement} {slots.default?.() || defaultElement}
</div> </div>
</Transition> </Transition>,
); );
}; };
}, },

View File

@ -1,49 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@backtop-prefix-cls: ~'@{ant-prefix}-back-top';
.@{backtop-prefix-cls} {
.reset-component();
position: fixed;
right: 100px;
bottom: 50px;
z-index: @zindex-back-top;
width: 40px;
height: 40px;
cursor: pointer;
&:empty {
display: none;
}
&-rtl {
right: auto;
left: 100px;
direction: rtl;
}
&-content {
width: 40px;
height: 40px;
overflow: hidden;
color: @back-top-color;
text-align: center;
background-color: @back-top-bg;
border-radius: 20px;
transition: all 0.3s;
&:hover {
background-color: @back-top-hover-bg;
transition: all 0.3s;
}
}
&-icon {
font-size: 24px;
line-height: 40px;
}
}
@import './responsive';

View File

@ -0,0 +1,118 @@
import type { CSSObject } from '../../_util/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../_style';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
zIndexPopup: number;
}
type BackTopToken = FullToken<'BackTop'> & {
backTopBackground: string;
backTopColor: string;
backTopHoverBackground: string;
backTopFontSize: number;
backTopSize: number;
// Position
backTopBlockEnd: number;
backTopInlineEnd: number;
backTopInlineEndMD: number;
backTopInlineEndXS: number;
};
// ============================== Shared ==============================
const genSharedBackTopStyle: GenerateStyle<BackTopToken, CSSObject> = (token): CSSObject => {
const { componentCls, backTopFontSize, backTopSize, zIndexPopup } = token;
return {
[componentCls]: {
...resetComponent(token),
position: 'fixed',
insetInlineEnd: token.backTopInlineEnd,
insetBlockEnd: token.backTopBlockEnd,
zIndex: zIndexPopup,
width: 40,
height: 40,
cursor: 'pointer',
'&:empty': {
display: 'none',
},
[`${componentCls}-content`]: {
width: backTopSize,
height: backTopSize,
overflow: 'hidden',
color: token.backTopColor,
textAlign: 'center',
backgroundColor: token.backTopBackground,
borderRadius: backTopSize,
transition: `all ${token.motionDurationMid}`,
'&:hover': {
backgroundColor: token.backTopHoverBackground,
transition: `all ${token.motionDurationMid}`,
},
},
// change to .backtop .backtop-icon
[`${componentCls}-icon`]: {
fontSize: backTopFontSize,
lineHeight: `${backTopSize}px`,
},
},
};
};
const genMediaBackTopStyle: GenerateStyle<BackTopToken> = (token): CSSObject => {
const { componentCls } = token;
return {
[`@media (max-width: ${token.screenMD}px)`]: {
[componentCls]: {
insetInlineEnd: token.backTopInlineEndMD,
},
},
[`@media (max-width: ${token.screenXS}px)`]: {
[componentCls]: {
insetInlineEnd: token.backTopInlineEndXS,
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook<'BackTop'>(
'BackTop',
token => {
const {
fontSizeHeading3,
colorTextDescription,
colorTextLightSolid,
colorText,
controlHeightLG,
} = token;
const backTopToken = mergeToken<BackTopToken>(token, {
backTopBackground: colorTextDescription,
backTopColor: colorTextLightSolid,
backTopHoverBackground: colorText,
backTopFontSize: fontSizeHeading3,
backTopSize: controlHeightLG,
backTopBlockEnd: controlHeightLG * 1.25,
backTopInlineEnd: controlHeightLG * 2.5,
backTopInlineEndMD: controlHeightLG * 1.5,
backTopInlineEndXS: controlHeightLG * 0.5,
});
return [genSharedBackTopStyle(backTopToken), genMediaBackTopStyle(backTopToken)];
},
token => ({
zIndexPopup: token.zIndexBase + 10,
}),
);

View File

@ -1,2 +0,0 @@
import '../../style/index.less';
import './index.less';

View File

@ -1,21 +0,0 @@
@media screen and (max-width: @screen-md) {
.@{backtop-prefix-cls} {
right: 60px;
&-rtl {
right: auto;
left: 60px;
}
}
}
@media screen and (max-width: @screen-xs) {
.@{backtop-prefix-cls} {
right: 20px;
&-rtl {
right: auto;
left: 20px;
}
}
}

View File

@ -28,7 +28,7 @@ import './switch/style';
import './auto-complete/style'; import './auto-complete/style';
// import './affix/style'; // import './affix/style';
import './cascader/style'; import './cascader/style';
import './back-top/style'; // import './back-top/style';
import './modal/style'; import './modal/style';
// import './alert/style'; // import './alert/style';
import './time-picker/style'; import './time-picker/style';

View File

@ -1,7 +1,7 @@
import type { ComponentToken as AlertComponentToken } from '../../alert/style'; import type { ComponentToken as AlertComponentToken } from '../../alert/style';
import type { ComponentToken as AnchorComponentToken } from '../../anchor/style'; import type { ComponentToken as AnchorComponentToken } from '../../anchor/style';
import type { ComponentToken as AvatarComponentToken } from '../../avatar/style'; import type { ComponentToken as AvatarComponentToken } from '../../avatar/style';
// import type { ComponentToken as BackTopComponentToken } from '../../back-top/style'; import type { ComponentToken as BackTopComponentToken } from '../../back-top/style';
// import type { ComponentToken as ButtonComponentToken } from '../../button/style'; // import type { ComponentToken as ButtonComponentToken } from '../../button/style';
// import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style'; // import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style';
// import type { ComponentToken as CalendarComponentToken } from '../../calendar/style'; // import type { ComponentToken as CalendarComponentToken } from '../../calendar/style';
@ -55,32 +55,32 @@ export interface ComponentTokenMap {
Alert?: AlertComponentToken; Alert?: AlertComponentToken;
Anchor?: AnchorComponentToken; Anchor?: AnchorComponentToken;
Avatar?: AvatarComponentToken; Avatar?: AvatarComponentToken;
// BackTop?: BackTopComponentToken; BackTop?: BackTopComponentToken;
// Badge?: {}; Badge?: {};
// Button?: ButtonComponentToken; // Button?: ButtonComponentToken;
// Breadcrumb?: {}; Breadcrumb?: {};
// Card?: CardComponentToken; // Card?: CardComponentToken;
// Carousel?: CarouselComponentToken; // Carousel?: CarouselComponentToken;
// Cascader?: CascaderComponentToken; // Cascader?: CascaderComponentToken;
// Checkbox?: CheckboxComponentToken; // Checkbox?: CheckboxComponentToken;
// Collapse?: CollapseComponentToken; // Collapse?: CollapseComponentToken;
// DatePicker?: DatePickerComponentToken; // DatePicker?: DatePickerComponentToken;
// Descriptions?: {}; Descriptions?: {};
// Divider?: DividerComponentToken; // Divider?: DividerComponentToken;
// Drawer?: DrawerComponentToken; // Drawer?: DrawerComponentToken;
// Dropdown?: DropdownComponentToken; // Dropdown?: DropdownComponentToken;
// Empty?: EmptyComponentToken; // Empty?: EmptyComponentToken;
// FloatButton?: FloatButtonComponentToken; // FloatButton?: FloatButtonComponentToken;
// Form?: {}; Form?: {};
// Grid?: {}; Grid?: {};
// Image?: ImageComponentToken; // Image?: ImageComponentToken;
// Input?: {}; Input?: {};
// InputNumber?: InputNumberComponentToken; // InputNumber?: InputNumberComponentToken;
// Layout?: LayoutComponentToken; // Layout?: LayoutComponentToken;
// List?: ListComponentToken; // List?: ListComponentToken;
// Mentions?: MentionsComponentToken; // Mentions?: MentionsComponentToken;
// Notification?: NotificationComponentToken; // Notification?: NotificationComponentToken;
// Pagination?: {}; Pagination?: {};
// Popover?: PopoverComponentToken; // Popover?: PopoverComponentToken;
// Popconfirm?: PopconfirmComponentToken; // Popconfirm?: PopconfirmComponentToken;
// Rate?: RateComponentToken; // Rate?: RateComponentToken;
@ -91,11 +91,11 @@ export interface ComponentTokenMap {
// Skeleton?: SkeletonComponentToken; // Skeleton?: SkeletonComponentToken;
// Slider?: SliderComponentToken; // Slider?: SliderComponentToken;
// Spin?: SpinComponentToken; // Spin?: SpinComponentToken;
// Statistic?: {}; Statistic?: {};
// Switch?: {}; Switch?: {};
// Tag?: TagComponentToken; // Tag?: TagComponentToken;
// Tree?: {}; Tree?: {};
// TreeSelect?: {}; TreeSelect?: {};
// Typography?: TypographyComponentToken; // Typography?: TypographyComponentToken;
// Timeline?: TimelineComponentToken; // Timeline?: TimelineComponentToken;
// Transfer?: TransferComponentToken; // Transfer?: TransferComponentToken;