-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Feat v4 floatbutton #6294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat v4 floatbutton #6294
Changes from 2 commits
72241e9
4bb6853
a40904d
3f03987
fc46ccd
4c4c668
1160fd3
675bedb
557f082
a426af9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined'; | ||
import { getTransitionProps, Transition } from '../_util/transition'; | ||
import { | ||
defineComponent, | ||
nextTick, | ||
onActivated, | ||
onBeforeUnmount, | ||
onMounted, | ||
reactive, | ||
ref, | ||
watch, | ||
onDeactivated, | ||
} from 'vue'; | ||
import FloatButton from './FloatButton'; | ||
import useConfigInject from '../config-provider/hooks/useConfigInject'; | ||
import getScroll from '../_util/getScroll'; | ||
import scrollTo from '../_util/scrollTo'; | ||
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame'; | ||
import { initDefaultProps } from '../_util/props-util'; | ||
import { backTopProps } from './interface'; | ||
import { floatButtonPrefixCls } from './FloatButton'; | ||
|
||
import useStyle from './style'; | ||
|
||
const BackTop = defineComponent({ | ||
compatConfig: { MODE: 3 }, | ||
name: 'ABackTop', | ||
inheritAttrs: false, | ||
props: initDefaultProps(backTopProps(), { | ||
visibilityHeight: 400, | ||
target: () => window, | ||
duration: 450, | ||
}), | ||
// emits: ['click'], | ||
setup(props, { slots, attrs, emit }) { | ||
const { prefixCls, direction } = useConfigInject(floatButtonPrefixCls, props); | ||
|
||
const [wrapSSR] = useStyle(prefixCls); | ||
|
||
const domRef = ref(); | ||
const state = reactive({ | ||
visible: false, | ||
scrollEvent: null, | ||
}); | ||
|
||
const getDefaultTarget = () => | ||
domRef.value && domRef.value.ownerDocument ? domRef.value.ownerDocument : window; | ||
|
||
const scrollToTop = (e: Event) => { | ||
const { target = getDefaultTarget, duration } = props; | ||
scrollTo(0, { | ||
getContainer: target, | ||
duration, | ||
}); | ||
emit('click', e); | ||
}; | ||
|
||
const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => { | ||
const { visibilityHeight } = props; | ||
const scrollTop = getScroll(e.target, true); | ||
state.visible = scrollTop >= visibilityHeight; | ||
}); | ||
|
||
const bindScrollEvent = () => { | ||
const { target } = props; | ||
const getTarget = target || getDefaultTarget; | ||
const container = getTarget(); | ||
handleScroll({ target: container }); | ||
container?.addEventListener('scroll', handleScroll); | ||
}; | ||
|
||
const scrollRemove = () => { | ||
const { target } = props; | ||
const getTarget = target || getDefaultTarget; | ||
const container = getTarget(); | ||
handleScroll.cancel(); | ||
container?.removeEventListener('scroll', handleScroll); | ||
}; | ||
|
||
watch( | ||
() => props.target, | ||
() => { | ||
scrollRemove(); | ||
nextTick(() => { | ||
bindScrollEvent(); | ||
}); | ||
}, | ||
); | ||
|
||
onMounted(() => { | ||
nextTick(() => { | ||
bindScrollEvent(); | ||
}); | ||
}); | ||
|
||
onActivated(() => { | ||
nextTick(() => { | ||
bindScrollEvent(); | ||
}); | ||
}); | ||
|
||
onDeactivated(() => { | ||
scrollRemove(); | ||
}); | ||
|
||
onBeforeUnmount(() => { | ||
scrollRemove(); | ||
}); | ||
|
||
return () => { | ||
const defaultElement = ( | ||
<div class={`${prefixCls.value}-content`}> | ||
<div class={`${prefixCls.value}-icon`}> | ||
<VerticalAlignTopOutlined /> | ||
</div> | ||
</div> | ||
); | ||
const divProps = { | ||
...attrs, | ||
onClick: scrollToTop, | ||
class: { | ||
[`${prefixCls.value}`]: true, | ||
[`${attrs.class}`]: attrs.class, | ||
[`${prefixCls.value}-rtl`]: direction.value === 'rtl', | ||
}, | ||
}; | ||
|
||
const transitionProps = getTransitionProps('fade'); | ||
return wrapSSR( | ||
<Transition {...transitionProps}> | ||
<FloatButton v-show={state.visible} {...divProps} ref={domRef}> | ||
{{ | ||
icon: () => <VerticalAlignTopOutlined />, | ||
default: () => slots.default?.() || defaultElement, | ||
}} | ||
</FloatButton> | ||
</Transition>, | ||
); | ||
}; | ||
}, | ||
}); | ||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
BackTop.displayName = 'BackTop'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 只有函数式组件才是 displayName,普通组件不需要 |
||
} | ||
|
||
export default BackTop; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import classNames from '../_util/classNames'; | ||
import { defineComponent, computed, CSSProperties, ref } from 'vue'; | ||
import Tooltip from '../tooltip'; | ||
import Content from './FloatButtonContent'; | ||
import type { FloatButtonContentProps } from './interface'; | ||
import useConfigInject from '../config-provider/hooks/useConfigInject'; | ||
import FloatButtonGroupContext from './context'; | ||
import warning from '../_util/warning'; | ||
import { initDefaultProps } from '../_util/props-util'; | ||
import { floatButtonProps } from './interface'; | ||
// import { useCompactItemContext } from '../space/Compact'; | ||
|
||
// CSSINJS | ||
import useStyle from './style'; | ||
|
||
export const floatButtonPrefixCls = 'float-btn'; | ||
|
||
const FloatButton = defineComponent({ | ||
compatConfig: { MODE: 3 }, | ||
name: 'AFloatButton', | ||
inheritAttrs: false, | ||
props: initDefaultProps(floatButtonProps(), { type: 'default', shape: 'circle' }), | ||
setup(props, { attrs, slots, expose }) { | ||
const { prefixCls, direction } = useConfigInject(floatButtonPrefixCls, props); | ||
const [wrapSSR, hashId] = useStyle(prefixCls); | ||
const { shape: groupShape } = FloatButtonGroupContext.useInject(); | ||
|
||
const floatButtonRef = ref(null); | ||
|
||
const mergeShape = computed(() => { | ||
return groupShape || props.shape; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. groupShape.value ? |
||
}); | ||
|
||
const classString = computed(() => { | ||
return classNames( | ||
hashId.value, | ||
prefixCls.value, | ||
attrs.class, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. attrs.class, 不是响应式的 |
||
`${prefixCls.value}-${props.type}`, | ||
`${prefixCls.value}-${mergeShape.value}`, | ||
{ | ||
[`${prefixCls.value}-rtl`]: direction.value === 'rtl', | ||
}, | ||
); | ||
}); | ||
|
||
expose({ | ||
floatButtonEl: floatButtonRef, | ||
}); | ||
|
||
return () => { | ||
const { | ||
prefixCls: customPrefixCls, | ||
type = 'default', | ||
shape = 'circle', | ||
description, | ||
tooltip, | ||
...restProps | ||
} = props; | ||
|
||
const contentProps: FloatButtonContentProps = { | ||
prefixCls: prefixCls.value, | ||
description, | ||
}; | ||
|
||
const buttonNode = ( | ||
<Tooltip placement="left"> | ||
{{ | ||
title: | ||
slots.tooltip || tooltip | ||
? () => (slots.tooltip && slots.tooltip()) || tooltip | ||
: undefined, | ||
default: () => ( | ||
<div class={`${prefixCls.value}-body`}> | ||
<Content {...contentProps}> | ||
{{ | ||
icon: slots.icon, | ||
description: slots.description, | ||
}} | ||
</Content> | ||
</div> | ||
), | ||
}} | ||
</Tooltip> | ||
); | ||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
warning( | ||
!(shape === 'circle' && description), | ||
'FloatButton', | ||
'supported only when `shape` is `square`. Due to narrow space for text, short sentence is recommended.', | ||
); | ||
} | ||
|
||
return wrapSSR( | ||
props.href ? ( | ||
<a | ||
ref={floatButtonRef} | ||
{...attrs} | ||
{...(restProps as any)} | ||
class={classString.value} | ||
style={attrs.style as CSSProperties} | ||
> | ||
{buttonNode} | ||
</a> | ||
) : ( | ||
<button | ||
ref={floatButtonRef} | ||
{...attrs} | ||
{...restProps} | ||
class={classString.value} | ||
style={attrs.style as CSSProperties} | ||
type="button" | ||
> | ||
{buttonNode} | ||
</button> | ||
), | ||
); | ||
}; | ||
}, | ||
}); | ||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
FloatButton.displayName = 'FloatButton'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 同上一个组件 |
||
} | ||
|
||
export default FloatButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { defineComponent } from 'vue'; | ||
import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined'; | ||
import classNames from '../_util/classNames'; | ||
import { initDefaultProps } from '../_util/props-util'; | ||
import { floatButtonContentProps } from './interface'; | ||
|
||
const FloatButtonContent = defineComponent({ | ||
compatConfig: { MODE: 3 }, | ||
name: 'AFloatButtonContent', | ||
inheritAttrs: false, | ||
props: initDefaultProps(floatButtonContentProps(), {}), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 没有默认值就不用加了 |
||
setup(props, { attrs, slots }) { | ||
return () => { | ||
const { description, prefixCls } = props; | ||
|
||
const defaultElement = ( | ||
<div class={`${prefixCls}-icon`}> | ||
<FileTextOutlined /> | ||
</div> | ||
); | ||
|
||
return ( | ||
<div {...attrs} class={classNames(attrs.class, `${prefixCls}-content`)}> | ||
{slots.icon || description ? ( | ||
<> | ||
{slots.icon && <div class={`${prefixCls}-icon`}>{slots.icon()}</div>} | ||
{(slots.description || description) && ( | ||
<div class={`${prefixCls}-description`}> | ||
{(slots.description && slots.description()) || description} | ||
</div> | ||
)} | ||
</> | ||
) : ( | ||
defaultElement | ||
)} | ||
</div> | ||
); | ||
}; | ||
}, | ||
}); | ||
|
||
export default FloatButtonContent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FloatButtonGroup 也要 export