refactor: float-button

pull/6348/head
tangjinzhou 2023-03-03 16:58:47 +08:00
parent 355c41b4aa
commit add208aefb
17 changed files with 136 additions and 146 deletions

View File

@ -33,7 +33,7 @@ export type BackTopProps = Partial<ExtractPropTypes<typeof backTopProps>>;
const BackTop = defineComponent({
compatConfig: { MODE: 3 },
name: 'ABackTop',
name: 'ABackTopLegacy',
inheritAttrs: false,
props: backTopProps(),
// emits: ['click'],

View File

@ -13,9 +13,6 @@ export { default as Alert } from './alert';
export type { AvatarProps } from './avatar';
export { default as Avatar, AvatarGroup } from './avatar';
export type { BackTopProps } from './back-top';
export { default as BackTop } from './back-top';
export type { BadgeProps } from './badge';
export { default as Badge, BadgeRibbon } from './badge';
@ -76,8 +73,12 @@ 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 {
FloatButtonProps,
FloatButtonGroupProps,
BackTopProps,
} from './float-button/interface';
export { default as FloatButton, FloatButtonGroup, BackTop } from './float-button';
export type { FormProps, FormItemProps, FormInstance, FormItemInstance } from './form';
export { default as Form, FormItem, FormItemRest } from './form';

View File

@ -11,16 +11,16 @@ import {
watch,
onDeactivated,
} from 'vue';
import FloatButton from './FloatButton';
import FloatButton, { floatButtonPrefixCls } 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';
import { useInjectFloatButtonGroupContext } from './context';
const BackTop = defineComponent({
compatConfig: { MODE: 3 },
@ -30,6 +30,8 @@ const BackTop = defineComponent({
visibilityHeight: 400,
target: () => window,
duration: 450,
type: 'default',
shape: 'circle',
}),
// emits: ['click'],
setup(props, { slots, attrs, emit }) {
@ -39,7 +41,7 @@ const BackTop = defineComponent({
const domRef = ref();
const state = reactive({
visible: false,
visible: props.visibilityHeight === 0,
scrollEvent: null,
});
@ -106,7 +108,7 @@ const BackTop = defineComponent({
onBeforeUnmount(() => {
scrollRemove();
});
const floatButtonGroupContext = useInjectFloatButtonGroupContext();
return () => {
const defaultElement = (
<div class={`${prefixCls.value}-content`}>
@ -115,8 +117,9 @@ const BackTop = defineComponent({
</div>
</div>
);
const divProps = {
const floatButtonProps = {
...attrs,
shape: floatButtonGroupContext?.shape.value || props.shape,
onClick: scrollToTop,
class: {
[`${prefixCls.value}`]: true,
@ -128,7 +131,7 @@ const BackTop = defineComponent({
const transitionProps = getTransitionProps('fade');
return wrapSSR(
<Transition {...transitionProps}>
<FloatButton v-show={state.visible} {...divProps} ref={domRef}>
<FloatButton v-show={state.visible} {...floatButtonProps} ref={domRef}>
{{
icon: () => <VerticalAlignTopOutlined />,
default: () => slots.default?.() || defaultElement,

View File

@ -1,10 +1,9 @@
import classNames from '../_util/classNames';
import { defineComponent, computed, CSSProperties, ref } from 'vue';
import { defineComponent, computed, 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 { useInjectFloatButtonGroupContext } from './context';
import warning from '../_util/warning';
import { initDefaultProps } from '../_util/props-util';
import { floatButtonProps } from './interface';
@ -20,39 +19,30 @@ const FloatButton = defineComponent({
name: 'AFloatButton',
inheritAttrs: false,
props: initDefaultProps(floatButtonProps(), { type: 'default', shape: 'circle' }),
setup(props, { attrs, slots, expose }) {
setup(props, { attrs, slots }) {
const { prefixCls, direction } = useConfigInject(floatButtonPrefixCls, props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const { shape: groupShape } = FloatButtonGroupContext.useInject();
const { shape: groupShape } = useInjectFloatButtonGroupContext();
const floatButtonRef = ref(null);
const floatButtonRef = ref<HTMLAnchorElement | HTMLButtonElement>(null);
const mergeShape = computed(() => {
return groupShape?.value || props.shape;
});
expose({
floatButtonEl: floatButtonRef,
});
return () => {
const {
prefixCls: customPrefixCls,
type = 'default',
shape = 'circle',
description,
description = slots.description?.(),
tooltip,
...restProps
} = props;
const contentProps: FloatButtonContentProps = {
prefixCls: prefixCls.value,
description,
};
const classString = classNames(
prefixCls.value,
`${prefixCls.value}-${props.type}`,
`${prefixCls.value}-${type}`,
`${prefixCls.value}-${mergeShape.value}`,
{
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
@ -62,24 +52,26 @@ const FloatButton = defineComponent({
);
const buttonNode = (
<Tooltip placement="left">
{{
<Tooltip
placement="left"
v-slots={{
title:
slots.tooltip || tooltip
? () => (slots.tooltip && slots.tooltip()) || tooltip
: undefined,
default: () => (
<div class={`${prefixCls.value}-body`}>
<Content {...contentProps}>
{{
<Content
prefixCls={prefixCls.value}
v-slots={{
icon: slots.icon,
description: slots.description,
description: () => description,
}}
</Content>
></Content>
</div>
),
}}
</Tooltip>
></Tooltip>
);
if (process.env.NODE_ENV !== 'production') {
@ -92,24 +84,11 @@ const FloatButton = defineComponent({
return wrapSSR(
props.href ? (
<a
ref={floatButtonRef}
{...attrs}
{...(restProps as any)}
class={classString}
style={attrs.style as CSSProperties}
>
<a ref={floatButtonRef} {...attrs} {...(restProps as any)} class={classString}>
{buttonNode}
</a>
) : (
<button
ref={floatButtonRef}
{...attrs}
{...restProps}
class={classString}
style={attrs.style as CSSProperties}
type="button"
>
<button ref={floatButtonRef} {...attrs} {...restProps} class={classString} type="button">
{buttonNode}
</button>
),

View File

@ -1,7 +1,7 @@
import { defineComponent } from 'vue';
import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined';
import classNames from '../_util/classNames';
import { floatButtonContentProps } from './interface';
import { filterEmpty } from '../_util/props-util';
const FloatButtonContent = defineComponent({
compatConfig: { MODE: 3 },
@ -10,27 +10,22 @@ const FloatButtonContent = defineComponent({
props: floatButtonContentProps(),
setup(props, { attrs, slots }) {
return () => {
const { description, prefixCls } = props;
const defaultElement = (
<div class={`${prefixCls}-icon`}>
<FileTextOutlined />
</div>
);
const { prefixCls } = props;
const description = filterEmpty(slots.description?.());
return (
<div {...attrs} class={classNames(attrs.class, `${prefixCls}-content`)}>
{slots.icon || description ? (
<div {...attrs} class={[attrs.class, `${prefixCls}-content`]}>
{slots.icon || description.length ? (
<>
{slots.icon && <div class={`${prefixCls}-icon`}>{slots.icon()}</div>}
{(slots.description || description) && (
<div class={`${prefixCls}-description`}>
{(slots.description && slots.description()) || description}
</div>
)}
{description.length ? (
<div class={`${prefixCls}-description`}>{description}</div>
) : null}
</>
) : (
defaultElement
<div class={`${prefixCls}-icon`}>
<FileTextOutlined />
</div>
)}
</div>
);

View File

@ -1,17 +1,18 @@
import { defineComponent, ref, computed, watch } from 'vue';
import { defineComponent, ref, computed, watch, onBeforeUnmount } from 'vue';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import FileTextOutlined from '@ant-design/icons-vue/FileTextOutlined';
import classNames from '../_util/classNames';
import { getTransitionProps, Transition } from '../_util/transition';
import FloatButton, { floatButtonPrefixCls } from './FloatButton';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import FloatButtonGroupContext from './context';
import { initDefaultProps } from '../_util/props-util';
import { useProvideFloatButtonGroupContext } from './context';
import { findDOMNode, initDefaultProps } from '../_util/props-util';
import { floatButtonGroupProps } from './interface';
import type { FloatButtonGroupProps } from './interface';
// CSSINJS
import useStyle from './style';
import useMergedState from '../_util/hooks/useMergedState';
const FloatButtonGroup = defineComponent({
compatConfig: { MODE: 3 },
@ -21,63 +22,68 @@ const FloatButtonGroup = defineComponent({
type: 'default',
shape: 'circle',
} as FloatButtonGroupProps),
setup(props, { attrs, slots }) {
setup(props, { attrs, slots, emit }) {
const { prefixCls, direction } = useConfigInject(floatButtonPrefixCls, props);
// style
const [wrapSSR, hashId] = useStyle(prefixCls);
const open = ref(props.open);
const [open, setOpen] = useMergedState(false, { value: computed(() => props.open) });
const floatButtonGroupRef = ref<HTMLDivElement>(null);
const floatButtonRef = ref<HTMLButtonElement | HTMLAnchorElement>(null);
FloatButtonGroupContext.useProvide({
useProvideFloatButtonGroupContext({
shape: computed(() => props.shape),
});
const hoverTypeAction = {
onMouseenter() {
setOpen(true);
emit('update:open', true);
props.onOpenChange?.(true);
},
onMouseleave() {
setOpen(false);
emit('update:open', false);
props.onOpenChange?.(false);
},
};
const hoverAction = computed(() => {
const hoverTypeAction = {
onMouseenter() {
open.value = true;
props.onOpenChange?.(true);
},
onMouseleave() {
open.value = false;
props.onOpenChange?.(false);
},
};
return props.trigger === 'hover' ? hoverTypeAction : {};
});
const handleOpenChange = () => {
open.value = !open.value;
props.onOpenChange?.(!open.value);
const nextOpen = !open.value;
emit('update:open', nextOpen);
props.onOpenChange?.(nextOpen);
setOpen(nextOpen);
};
const onClick = (e: MouseEvent) => {
if (floatButtonGroupRef.value?.contains(e.target as Node)) {
if ((floatButtonRef.value as any)?.floatButtonEl?.contains(e.target as Node)) {
if (findDOMNode(floatButtonRef.value)?.contains(e.target as Node)) {
handleOpenChange();
}
return;
}
open.value = false;
setOpen(false);
emit('update:open', false);
props.onOpenChange?.(false);
};
watch(
computed(() => props.trigger),
value => {
document.removeEventListener('click', onClick);
if (value === 'click') {
document.addEventListener('click', onClick);
return () => {
document.removeEventListener('click', onClick);
};
}
},
{ immediate: true },
);
onBeforeUnmount(() => {
document.removeEventListener('click', onClick);
});
return () => {
const { shape = 'circle', type = 'default', tooltip, description, trigger } = props;
@ -99,7 +105,7 @@ const FloatButtonGroup = defineComponent({
{trigger && ['click', 'hover'].includes(trigger) ? (
<>
<Transition {...transitionProps}>
<div v-show={open.value} class={classNames(wrapperCls)}>
<div v-show={open.value} class={wrapperCls}>
{slots.default && slots.default()}
</div>
</Transition>
@ -109,19 +115,18 @@ const FloatButtonGroup = defineComponent({
shape={shape}
tooltip={tooltip}
description={description}
>
{{
v-slots={{
icon: () =>
open.value
? (slots.closeIcon && slots.closeIcon()) || <CloseOutlined />
: (slots.icon && slots.icon()) || <FileTextOutlined />,
? slots.closeIcon?.() || <CloseOutlined />
: slots.icon?.() || <FileTextOutlined />,
tooltip: slots.tooltip,
description: slots.description,
}}
</FloatButton>
></FloatButton>
</>
) : (
slots.default && slots.default()
slots.default?.()
)}
</div>,
);

View File

@ -1,29 +1,19 @@
import type { Ref } from 'vue';
import { inject, provide } from 'vue';
import type { Ref, InjectionKey } from 'vue';
import { inject, provide, ref } from 'vue';
import type { FloatButtonShape } from './interface';
function createContext<T extends Record<string, any>>(defaultValue?: T) {
const contextKey = Symbol('floatButtonGroupContext');
const useProvide = (props: T) => {
provide(contextKey, props);
return props;
};
const useInject = () => {
return inject(contextKey, defaultValue as T) || ({} as T);
};
return {
useProvide,
useInject,
};
interface FloatButtonGroupContext {
shape: Ref<FloatButtonShape>;
}
const contextKey: InjectionKey<FloatButtonGroupContext> = Symbol('floatButtonGroupContext');
const FloatButtonGroupContext = createContext<{ shape: Ref<FloatButtonShape> } | undefined>(
undefined,
);
export const useProvideFloatButtonGroupContext = (props: FloatButtonGroupContext) => {
provide(contextKey, props);
export default FloatButtonGroupContext;
return props;
};
export const useInjectFloatButtonGroupContext = () => {
return inject(contextKey, { shape: ref() } as FloatButtonGroupContext);
};

View File

@ -32,10 +32,8 @@ title:
<script lang="ts">
import { defineComponent } from 'vue';
import { FileTextOutlined } from '@ant-design/icons-vue';
export default defineComponent({
components: { FileTextOutlined },
setup() {
return {};
},

View File

@ -25,7 +25,7 @@ When multiple buttons are used together, `<FloatButton.Group />` is recommended.
</template>
</a-float-button>
<a-float-button />
<a-back-top :visibilityHeight="0" />
<a-back-top :visibility-height="0" />
</a-float-button-group>
<a-float-button-group shape="square" :style="{ right: '94px' }">
<a-float-button>
@ -40,7 +40,7 @@ When multiple buttons are used together, `<FloatButton.Group />` is recommended.
<SyncOutlined />
</template>
</a-float-button>
<a-back-top :visibilityHeight="0" />
<a-back-top :visibility-height="0" />
</a-float-button-group>
</template>

View File

@ -25,18 +25,28 @@ FloatButton. Available since `4.0.0`.
| tooltip | The text shown in the tooltip | string \| slot | | |
| type | Setting button type | `default` \| `primary` | `default` | |
| shape | Setting button shape | `circle` \| `square` | `circle` | |
| onClick | Set the handler to handle `click` event | (event) => void | - | |
| href | The target of hyperlink | string | - | |
| target | Specifies where to display the linked URL | string | - | |
### common events
| Events Name | Description | Arguments | Version |
| ----------- | --------------------------------------- | ----------------- | ------- |
| click | Set the handler to handle `click` event | `(event) => void` | - |
### FloatButton.Group
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| shape | Setting button shape of children | `circle` \| `square` | `circle` | |
| trigger | Which action can trigger menu open/close | `click` \| `hover` | - | |
| open | Whether the menu is visible or not | boolean | - | |
| onOpenChange | Callback executed when active menu is changed | (open: boolean) => void | - | |
| open(v-model) | Whether the menu is visible or not | boolean | - | |
### FloatButton.Group Events
| Events Name | Description | Arguments | Version |
| ----------- | --------------------------------------------- | ----------------------- | ------- |
| openChange | Callback executed when active menu is changed | (open: boolean) => void | - |
### FloatButton.BackTop
@ -45,4 +55,3 @@ FloatButton. Available since `4.0.0`.
| duration | Time to return to topms | number | 450 | |
| target | Specifies the scrollable area dom node | () => HTMLElement | () => window | |
| visibilityHeight | The BackTop button will not show until the scroll height reaches this value | number | 400 | |
| onClick | A callback function, which can be executed when you click the button | () => void | - | |

View File

@ -33,7 +33,7 @@ FloatButton.install = function (app: App) {
return app;
};
export { FloatButtonGroup };
export { FloatButtonGroup, BackTop };
export default FloatButton as typeof FloatButton &
Plugin & {

View File

@ -30,14 +30,25 @@ cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HS-wTIIwu0kAAAAAAA
| href | 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 | string | - | |
| target | 相当于 a 标签的 target 属性href 存在时生效 | string | - | |
### common events
| 事件名称 | 说明 | 回调参数 | 版本 |
| -------- | --------------------------------------- | ----------------- | ---- |
| click | Set the handler to handle `click` event | `(event) => void` | - |
### FloatButton.Group
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ------------ | -------------------------------- | ----------------------- | -------- | ---- |
| shape | 设置包含的 FloatButton 按钮形状 | `circle` \| `square` | `circle` | |
| trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | |
| open | 受控展开 | boolean | - | |
| onOpenChange | 展开收起时的回调 | (open: boolean) => void | - | |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ------------- | -------------------------------- | -------------------- | -------- | ---- |
| shape | 设置包含的 FloatButton 按钮形状 | `circle` \| `square` | `circle` | |
| trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | |
| open(v-model) | 受控展开 | boolean | - | |
### FloatButton.Group Events
| 事件名称 | 说明 | 回调参数 | 版本 |
| ---------- | ---------------- | ----------------------- | ---- |
| openChange | 展开收起时的回调 | (open: boolean) => void | - |
### FloatButton.BackTop

View File

@ -27,7 +27,6 @@ export type FloatButtonProps = Partial<ExtractPropTypes<ReturnType<typeof floatB
export const floatButtonContentProps = () => {
return {
prefixCls: stringType<FloatButtonProps['prefixCls']>(),
description: PropTypes.any,
};
};
@ -42,9 +41,10 @@ export const floatButtonGroupProps = () => {
// 触发方式 (有触发方式为菜单模式)
trigger: stringType<FloatButtonGroupTrigger>(),
// 受控展开
open: booleanType(false),
open: booleanType(),
// 展开收起的回调
onOpenChange: functionType<(open: boolean) => void>(),
'onUpdate:open': functionType<(open: boolean) => void>(),
};
};

View File

@ -12,7 +12,7 @@ Used when the link needs to be converted into a QR Code.
## API
| Property | Description | Type | Default |
| :-- | :-- | :-- | :-- |
| --- | --- | --- | --- |
| value | scanned link | string | - |
| icon | include image url (only image link are supported) | string | - |
| size | QRCode size | number | 128 |
@ -25,7 +25,7 @@ Used when the link needs to be converted into a QR Code.
### events
| Events Name | Description | Arguments | Version |
| :---------- | :---------- | :----------- | :------ |
| ----------- | ----------- | ------------ | ------- |
| refresh | callback | `() => void` | - |
## FAQ

View File

@ -1,5 +1,5 @@
import { defineComponent, computed, ref } from 'vue';
import type { CSSProperties } from 'vue';
import type { CSSProperties, ExtractPropTypes } from 'vue';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import useStyle from './style';
import { useLocaleReceiver } from '../locale/LocaleReceiver';
@ -10,9 +10,9 @@ import { ReloadOutlined } from '@ant-design/icons-vue';
import { useToken } from '../theme/internal';
import { QRCodeCanvas } from './QRCodeCanvas';
import warning from '../_util/warning';
import type { QRCodeProps } from './interface';
import { qrcodeProps } from './interface';
export type QRCodeProps = Partial<ExtractPropTypes<ReturnType<typeof qrcodeProps>>>;
const QRCode = defineComponent({
name: 'AQrcode',
inheritAttrs: false,

View File

@ -13,7 +13,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*M4PBTZ_n9OgAAA
## API
| 参数 | 说明 | 类型 | 默认值 |
| :-- | :-- | :-- | :-- |
| --- | --- | --- | --- |
| value | 扫描后的地址 | string | - |
| icon | 二维码中图片的地址(目前只支持图片地址) | string | - |
| size | 二维码大小 | number | 160 |
@ -26,7 +26,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*M4PBTZ_n9OgAAA
### 事件
| 事件名称 | 说明 | 回调参数 | 版本 |
| :------- | :------------------- | :----------- | :--- |
| -------- | -------------------- | ------------ | ---- |
| refresh | 点击"点击刷新"的回调 | `() => void` | - |
## FAQ

View File

@ -1,5 +1,5 @@
import { objectType, stringType } from '../_util/type';
import type { ExtractPropTypes } from 'vue';
interface ImageSettings {
src: string;
height: number;
@ -30,7 +30,6 @@ export const qrcodeProps = () => {
bordered: { type: Boolean, default: true },
};
};
export type QRCodeProps = Partial<ExtractPropTypes<ReturnType<typeof qrcodeProps>>>;
export interface QRCodeCanvasColor {
dark?: string; // 默认#000000ff