Skip to content
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

Merged
merged 10 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export { default as Drawer } from './drawer';
export type { EmptyProps } from './empty';
export { default as Empty } from './empty';

export type { FloatButtonProps, FloatButtonGroupProps } from './float-button/interface';
export { default as FloatButton, FloatButtonGroup } from './float-button';

export type { FormProps, FormItemProps, FormInstance, FormItemInstance } from './form';
export { default as Form, FormItem, FormItemRest } from './form';

Expand Down
143 changes: 143 additions & 0 deletions components/float-button/BackTop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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>,
);
};
},
});

export default BackTop;
121 changes: 121 additions & 0 deletions components/float-button/FloatButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
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?.value || props.shape;
});

expose({
floatButtonEl: floatButtonRef,
});

return () => {
const {
prefixCls: customPrefixCls,
type = 'default',
shape = 'circle',
description,
tooltip,
...restProps
} = props;

const contentProps: FloatButtonContentProps = {
prefixCls: prefixCls.value,
description,
};

const classString = classNames(
prefixCls.value,
`${prefixCls.value}-${props.type}`,
`${prefixCls.value}-${mergeShape.value}`,
{
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
},
attrs.class,
hashId.value,
);

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}
style={attrs.style as CSSProperties}
>
{buttonNode}
</a>
) : (
<button
ref={floatButtonRef}
{...attrs}
{...restProps}
class={classString}
style={attrs.style as CSSProperties}
type="button"
>
{buttonNode}
</button>
),
);
};
},
});

export default FloatButton;
41 changes: 41 additions & 0 deletions components/float-button/FloatButtonContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineComponent } from 'vue';
import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined';
import classNames from '../_util/classNames';
import { floatButtonContentProps } from './interface';

const FloatButtonContent = defineComponent({
compatConfig: { MODE: 3 },
name: 'AFloatButtonContent',
inheritAttrs: false,
props: floatButtonContentProps(),
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;
Loading