mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
feat: FloatButton support Badge (#41040)
* feat: FloatButton support internal Badge * update docs * style * update docs * test: update snap * fix * update demo * test: update snap * update style * test: update snap * test: update snap * add docs * fix: update style * update * fix * fix * fix * update style * fix * update demo * add * add docs * snap * test: add test case * Update components/float-button/demo/badge.md Co-authored-by: MadCcc <1075746765@qq.com> * fix * docs: add debug demo * docs: add debug demo * test: update snap * fix border-radius * update snap * fix * fix * rename * style: use paddingXXS * fix * update demo * update snap * demo: add demo --------- Co-authored-by: MadCcc <1075746765@qq.com>
This commit is contained in:
parent
f06220d756
commit
51b973d9d5
@ -1,5 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import omit from 'rc-util/lib/omit';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import Badge from '../badge';
|
||||
import type { ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import Tooltip from '../tooltip';
|
||||
@ -8,6 +10,7 @@ import FloatButtonGroupContext from './context';
|
||||
import Content from './FloatButtonContent';
|
||||
import type {
|
||||
CompoundedComponent,
|
||||
FloatButtonBadgeProps,
|
||||
FloatButtonContentProps,
|
||||
FloatButtonProps,
|
||||
FloatButtonShape,
|
||||
@ -29,6 +32,7 @@ const FloatButton: React.ForwardRefRenderFunction<
|
||||
icon,
|
||||
description,
|
||||
tooltip,
|
||||
badge = {},
|
||||
...restProps
|
||||
} = props;
|
||||
const { getPrefixCls, direction } = useContext<ConfigConsumerProps>(ConfigContext);
|
||||
@ -50,16 +54,24 @@ const FloatButton: React.ForwardRefRenderFunction<
|
||||
},
|
||||
);
|
||||
|
||||
// 虽然在 ts 中已经 omit 过了,但是为了防止多余的属性被透传进来,这里再 omit 一遍,以防万一
|
||||
const badgeProps = useMemo<FloatButtonBadgeProps>(
|
||||
() => omit(badge, ['title', 'children', 'status', 'text', 'size'] as any[]),
|
||||
[badge],
|
||||
);
|
||||
|
||||
const contentProps = useMemo<FloatButtonContentProps>(
|
||||
() => ({ prefixCls, description, icon, type }),
|
||||
[prefixCls, description, icon, type],
|
||||
);
|
||||
|
||||
const buttonNode = (
|
||||
const buttonNode: React.ReactNode = (
|
||||
<Tooltip title={tooltip} placement={direction === 'rtl' ? 'right' : 'left'}>
|
||||
<div className={`${prefixCls}-body`}>
|
||||
<Content {...contentProps} />
|
||||
</div>
|
||||
<Badge {...badgeProps}>
|
||||
<div className={`${prefixCls}-body`}>
|
||||
<Content {...contentProps} />
|
||||
</div>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,109 +8,121 @@ exports[`FloatButtonGroup should correct render 1`] = `
|
||||
class="ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-body"
|
||||
<span
|
||||
class="ant-badge"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-body"
|
||||
<span
|
||||
class="ant-badge"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-body"
|
||||
<span
|
||||
class="ant-badge"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
@ -5,37 +5,41 @@ exports[`FloatButton rtl render component should be rendered correctly in RTL di
|
||||
class="ant-float-btn ant-float-btn-default ant-float-btn-circle ant-float-btn-rtl"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-body"
|
||||
<span
|
||||
class="ant-badge ant-badge-rtl"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
@ -44,36 +48,40 @@ exports[`FloatButton should correct render 1`] = `
|
||||
class="ant-float-btn ant-float-btn-default ant-float-btn-circle"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-body"
|
||||
<span
|
||||
class="ant-badge"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-content"
|
||||
class="ant-float-btn-body"
|
||||
>
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
class="ant-float-btn-content"
|
||||
>
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
<div
|
||||
class="ant-float-btn-icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="file-text"
|
||||
class="anticon anticon-file-text"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="file-text"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
@ -3,6 +3,7 @@ import FloatButton from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import getOffset from '../util';
|
||||
|
||||
describe('FloatButton', () => {
|
||||
mountTest(FloatButton);
|
||||
@ -62,4 +63,11 @@ describe('FloatButton', () => {
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('getOffset should return 0 when radius is 0', () => {
|
||||
const result1 = getOffset(0);
|
||||
expect(result1).toBe(0);
|
||||
const result2 = getOffset(1);
|
||||
expect(result2).not.toBe(0);
|
||||
});
|
||||
});
|
||||
|
7
components/float-button/demo/badge-debug.md
Normal file
7
components/float-button/demo/badge-debug.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
调试使用。
|
||||
|
||||
## en-US
|
||||
|
||||
debug use.
|
22
components/float-button/demo/badge-debug.tsx
Normal file
22
components/float-button/demo/badge-debug.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { ConfigProvider, FloatButton, Slider } from 'antd';
|
||||
import type { AliasToken } from 'antd/es/theme/interface';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [radius, setRadius] = useState<number>(0);
|
||||
|
||||
const token: Partial<AliasToken> = {
|
||||
borderRadius: radius,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Slider min={0} max={20} style={{ margin: 16 }} onChange={setRadius} />
|
||||
<ConfigProvider theme={{ token }}>
|
||||
<FloatButton shape="square" badge={{ dot: true }} />
|
||||
</ConfigProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
7
components/float-button/demo/badge.md
Normal file
7
components/float-button/demo/badge.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
右上角附带圆形徽标数字的悬浮按钮。
|
||||
|
||||
## en-US
|
||||
|
||||
FloatButton with Badge.
|
20
components/float-button/demo/badge.tsx
Normal file
20
components/float-button/demo/badge.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { FloatButton } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<FloatButton shape="circle" badge={{ dot: true }} style={{ right: 24 + 70 + 70 }} />
|
||||
<FloatButton.Group shape="circle" style={{ right: 24 + 70 }}>
|
||||
<FloatButton tooltip={<div>custom badge color</div>} badge={{ count: 5, color: 'blue' }} />
|
||||
<FloatButton badge={{ count: 5 }} />
|
||||
</FloatButton.Group>
|
||||
<FloatButton.Group shape="circle">
|
||||
<FloatButton badge={{ count: 12 }} icon={<QuestionCircleOutlined />} />
|
||||
<FloatButton badge={{ count: 123, overflowCount: 999 }} />
|
||||
<FloatButton.BackTop visibilityHeight={0} />
|
||||
</FloatButton.Group>
|
||||
</>
|
||||
);
|
||||
|
||||
export default App;
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { FloatButton } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
/** Test usage. Do not use in your production. */
|
||||
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalFloatButton } = FloatButton;
|
||||
|
||||
export default () => (
|
||||
const App: React.FC = () => (
|
||||
<div style={{ display: 'flex', columnGap: 16, alignItems: 'center' }}>
|
||||
<InternalFloatButton backTop />
|
||||
<InternalFloatButton icon={<CustomerServiceOutlined />} />
|
||||
@ -18,32 +18,22 @@ export default () => (
|
||||
<InternalFloatButton
|
||||
shape="square"
|
||||
items={[
|
||||
{
|
||||
icon: <QuestionCircleOutlined />,
|
||||
},
|
||||
{
|
||||
icon: <CustomerServiceOutlined />,
|
||||
},
|
||||
{
|
||||
icon: <SyncOutlined />,
|
||||
},
|
||||
{ icon: <QuestionCircleOutlined /> },
|
||||
{ icon: <CustomerServiceOutlined /> },
|
||||
{ icon: <SyncOutlined /> },
|
||||
]}
|
||||
/>
|
||||
<InternalFloatButton
|
||||
open
|
||||
icon={<CustomerServiceOutlined />}
|
||||
trigger="click"
|
||||
open
|
||||
items={[
|
||||
{
|
||||
icon: <QuestionCircleOutlined />,
|
||||
},
|
||||
{
|
||||
icon: <CustomerServiceOutlined />,
|
||||
},
|
||||
{
|
||||
icon: <SyncOutlined />,
|
||||
},
|
||||
{ icon: <QuestionCircleOutlined /> },
|
||||
{ icon: <CustomerServiceOutlined /> },
|
||||
{ icon: <SyncOutlined /> },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
@ -26,6 +26,8 @@ FloatButton. Available since `5.0.0`.
|
||||
<code src="./demo/group.tsx" iframe="360">FloatButton Group</code>
|
||||
<code src="./demo/group-menu.tsx" iframe="360">Menu mode</code>
|
||||
<code src="./demo/back-top.tsx" iframe="360">BackTop</code>
|
||||
<code src="./demo/badge.tsx" iframe="360">badge</code>
|
||||
<code src="./demo/badge-debug.tsx" iframe="360" debug>debug dot</code>
|
||||
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
|
||||
|
||||
## API
|
||||
@ -44,6 +46,7 @@ FloatButton. Available since `5.0.0`.
|
||||
| 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 | - | |
|
||||
| badge | float-button with round logo number | refer [badge](/components/badge#api) | - | 5.4.0 |
|
||||
|
||||
### FloatButton.Group
|
||||
|
||||
|
@ -27,6 +27,8 @@ demo:
|
||||
<code src="./demo/group.tsx" iframe="360">浮动按钮组</code>
|
||||
<code src="./demo/group-menu.tsx" iframe="360">菜单模式</code>
|
||||
<code src="./demo/back-top.tsx" iframe="360">回到顶部</code>
|
||||
<code src="./demo/badge.tsx" iframe="360">徽标数</code>
|
||||
<code src="./demo/badge-debug.tsx" iframe="360" debug>调试小圆点使用</code>
|
||||
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
|
||||
|
||||
## API
|
||||
@ -45,6 +47,7 @@ demo:
|
||||
| onClick | 点击按钮时的回调 | (event) => void | - | |
|
||||
| href | 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 | string | - | |
|
||||
| target | 相当于 a 标签的 target 属性,href 存在时生效 | string | - | |
|
||||
| badge | 带圆形徽标数字的悬浮按钮 | 参考[badge](/components/badge-cn#api) | - | 5.4.0 |
|
||||
|
||||
### FloatButton.Group
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import type React from 'react';
|
||||
import type Group from './FloatButtonGroup';
|
||||
import type BackTop from './BackTop';
|
||||
import type { BadgeProps } from '../badge';
|
||||
import type { TooltipProps } from '../tooltip';
|
||||
import type BackTop from './BackTop';
|
||||
import type Group from './FloatButtonGroup';
|
||||
import type PurePanel from './PurePanel';
|
||||
|
||||
export type FloatButtonType = 'default' | 'primary';
|
||||
@ -10,6 +11,11 @@ export type FloatButtonShape = 'circle' | 'square';
|
||||
|
||||
export type FloatButtonGroupTrigger = 'click' | 'hover';
|
||||
|
||||
export type FloatButtonBadgeProps = Omit<
|
||||
BadgeProps,
|
||||
'status' | 'text' | 'size' | 'title' | 'children'
|
||||
>;
|
||||
|
||||
export interface FloatButtonProps {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
@ -22,6 +28,7 @@ export interface FloatButtonProps {
|
||||
tooltip?: TooltipProps['title'];
|
||||
href?: string;
|
||||
target?: React.HTMLAttributeAnchorTarget;
|
||||
badge?: FloatButtonBadgeProps;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import type { CSSObject } from '@ant-design/cssinjs';
|
||||
import { Keyframes } from '@ant-design/cssinjs';
|
||||
import { resetComponent } from '../../style';
|
||||
import { initFadeMotion } from '../../style/motion/fade';
|
||||
import { initMotion } from '../../style/motion/motion';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { initFadeMotion } from '../../style/motion/fade';
|
||||
import { resetComponent } from '../../style';
|
||||
import { initMotion } from '../../style/motion/motion';
|
||||
import getOffset from '../util';
|
||||
|
||||
/** Component only token. Which will handle additional calculation of alias token */
|
||||
export interface ComponentToken {
|
||||
@ -18,6 +19,11 @@ type FloatButtonToken = FullToken<'FloatButton'> & {
|
||||
floatButtonFontSize: number;
|
||||
floatButtonSize: number;
|
||||
floatButtonIconSize: number;
|
||||
floatButtonBodySize: number;
|
||||
floatButtonBodyPadding: number;
|
||||
badgeOffset: number;
|
||||
dotOffsetInCircle: number;
|
||||
dotOffsetInSquare: number;
|
||||
|
||||
// Position
|
||||
floatButtonInsetBlockEnd: number;
|
||||
@ -33,20 +39,19 @@ const initFloatButtonGroupMotion = (token: FloatButtonToken) => {
|
||||
transformOrigin: '0 0',
|
||||
opacity: 0,
|
||||
},
|
||||
|
||||
'100%': {
|
||||
transform: 'translate3d(0, 0, 0)',
|
||||
transformOrigin: '0 0',
|
||||
opacity: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const moveDownOut = new Keyframes('antFloatButtonMoveDownOut', {
|
||||
'0%': {
|
||||
transform: 'translate3d(0, 0, 0)',
|
||||
transformOrigin: '0 0',
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
'100%': {
|
||||
transform: `translate3d(0, ${floatButtonSize}px, 0)`,
|
||||
transformOrigin: '0 0',
|
||||
@ -69,7 +74,6 @@ const initFloatButtonGroupMotion = (token: FloatButtonToken) => {
|
||||
opacity: 0,
|
||||
animationTimingFunction: motionEaseInOutCirc,
|
||||
},
|
||||
|
||||
[`&${groupPrefixCls}-wrap-leave`]: {
|
||||
animationTimingFunction: motionEaseInOutCirc,
|
||||
},
|
||||
@ -80,7 +84,16 @@ const initFloatButtonGroupMotion = (token: FloatButtonToken) => {
|
||||
|
||||
// ============================== Group ==============================
|
||||
const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token) => {
|
||||
const { componentCls, floatButtonSize, margin, borderRadiusLG } = token;
|
||||
const {
|
||||
antCls,
|
||||
componentCls,
|
||||
floatButtonSize,
|
||||
margin,
|
||||
borderRadiusLG,
|
||||
borderRadiusSM,
|
||||
badgeOffset,
|
||||
floatButtonBodyPadding,
|
||||
} = token;
|
||||
const groupPrefixCls = `${componentCls}-group`;
|
||||
return {
|
||||
[groupPrefixCls]: {
|
||||
@ -116,6 +129,7 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
|
||||
[`${componentCls}-body`]: {
|
||||
width: floatButtonSize,
|
||||
height: floatButtonSize,
|
||||
borderRadius: '50%',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -134,17 +148,22 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`,
|
||||
},
|
||||
[`${antCls}-badge`]: {
|
||||
[`${antCls}-badge-count`]: {
|
||||
top: -(floatButtonBodyPadding + badgeOffset),
|
||||
insetInlineEnd: -(floatButtonBodyPadding + badgeOffset),
|
||||
},
|
||||
},
|
||||
},
|
||||
[`${groupPrefixCls}-wrap`]: {
|
||||
display: 'block',
|
||||
borderRadius: borderRadiusLG,
|
||||
boxShadow: token.boxShadowSecondary,
|
||||
overflow: 'hidden',
|
||||
[`${componentCls}-square`]: {
|
||||
boxShadow: 'none',
|
||||
marginTop: 0,
|
||||
borderRadius: 0,
|
||||
padding: token.paddingXXS,
|
||||
padding: floatButtonBodyPadding,
|
||||
'&:first-child': {
|
||||
borderStartStartRadius: borderRadiusLG,
|
||||
borderStartEndRadius: borderRadiusLG,
|
||||
@ -157,13 +176,12 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
|
||||
borderBottom: `${token.lineWidth}px ${token.lineType} ${token.colorSplit}`,
|
||||
},
|
||||
[`${componentCls}-body`]: {
|
||||
width: floatButtonSize - token.paddingXXS * 2,
|
||||
height: floatButtonSize - token.paddingXXS * 2,
|
||||
width: token.floatButtonBodySize,
|
||||
height: token.floatButtonBodySize,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
[`${groupPrefixCls}-circle-shadow`]: {
|
||||
boxShadow: 'none',
|
||||
},
|
||||
@ -171,10 +189,11 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
|
||||
boxShadow: token.boxShadowSecondary,
|
||||
[`${componentCls}-square`]: {
|
||||
boxShadow: 'none',
|
||||
padding: token.paddingXXS,
|
||||
padding: floatButtonBodyPadding,
|
||||
[`${componentCls}-body`]: {
|
||||
width: floatButtonSize - token.paddingXXS * 2,
|
||||
height: floatButtonSize - token.paddingXXS * 2,
|
||||
width: token.floatButtonBodySize,
|
||||
height: token.floatButtonBodySize,
|
||||
borderRadius: borderRadiusSM,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -183,14 +202,23 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
|
||||
|
||||
// ============================== Shared ==============================
|
||||
const sharedFloatButtonStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token) => {
|
||||
const { componentCls, floatButtonIconSize, floatButtonSize, borderRadiusLG } = token;
|
||||
const {
|
||||
antCls,
|
||||
componentCls,
|
||||
floatButtonBodyPadding,
|
||||
floatButtonIconSize,
|
||||
floatButtonSize,
|
||||
borderRadiusLG,
|
||||
badgeOffset,
|
||||
dotOffsetInSquare,
|
||||
dotOffsetInCircle,
|
||||
} = token;
|
||||
return {
|
||||
[componentCls]: {
|
||||
...resetComponent(token),
|
||||
border: 'none',
|
||||
position: 'fixed',
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
zIndex: 99,
|
||||
display: 'block',
|
||||
justifyContent: 'center',
|
||||
@ -200,17 +228,24 @@ const sharedFloatButtonStyle: GenerateStyle<FloatButtonToken, CSSObject> = (toke
|
||||
insetInlineEnd: token.floatButtonInsetInlineEnd,
|
||||
insetBlockEnd: token.floatButtonInsetBlockEnd,
|
||||
boxShadow: token.boxShadowSecondary,
|
||||
|
||||
// Pure Panel
|
||||
'&-pure': {
|
||||
position: 'relative',
|
||||
inset: 'auto',
|
||||
},
|
||||
|
||||
'&:empty': {
|
||||
display: 'none',
|
||||
},
|
||||
|
||||
[`${antCls}-badge`]: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
[`${antCls}-badge-count`]: {
|
||||
transform: 'translate(0, 0)',
|
||||
transformOrigin: 'center',
|
||||
top: -badgeOffset,
|
||||
insetInlineEnd: -badgeOffset,
|
||||
},
|
||||
},
|
||||
[`${componentCls}-body`]: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@ -226,7 +261,7 @@ const sharedFloatButtonStyle: GenerateStyle<FloatButtonToken, CSSObject> = (toke
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: `2px 4px`,
|
||||
padding: `${floatButtonBodyPadding / 2}px ${floatButtonBodyPadding}px`,
|
||||
[`${componentCls}-icon`]: {
|
||||
textAlign: 'center',
|
||||
margin: 'auto',
|
||||
@ -237,12 +272,19 @@ const sharedFloatButtonStyle: GenerateStyle<FloatButtonToken, CSSObject> = (toke
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-rtl`]: {
|
||||
direction: 'rtl',
|
||||
},
|
||||
[`${componentCls}-circle`]: {
|
||||
height: floatButtonSize,
|
||||
borderRadius: '50%',
|
||||
[`${antCls}-badge`]: {
|
||||
[`${antCls}-badge-dot`]: {
|
||||
top: dotOffsetInCircle,
|
||||
insetInlineEnd: dotOffsetInCircle,
|
||||
},
|
||||
},
|
||||
[`${componentCls}-body`]: {
|
||||
borderRadius: '50%',
|
||||
},
|
||||
@ -251,9 +293,15 @@ const sharedFloatButtonStyle: GenerateStyle<FloatButtonToken, CSSObject> = (toke
|
||||
height: 'auto',
|
||||
minHeight: floatButtonSize,
|
||||
borderRadius: borderRadiusLG,
|
||||
[`${antCls}-badge`]: {
|
||||
[`${antCls}-badge-dot`]: {
|
||||
top: dotOffsetInSquare,
|
||||
insetInlineEnd: dotOffsetInSquare,
|
||||
},
|
||||
},
|
||||
[`${componentCls}-body`]: {
|
||||
height: 'auto',
|
||||
borderRadius: token.borderRadiusSM,
|
||||
borderRadius: borderRadiusLG,
|
||||
},
|
||||
},
|
||||
[`${componentCls}-default`]: {
|
||||
@ -315,7 +363,10 @@ export default genComponentStyleHook<'FloatButton'>('FloatButton', (token) => {
|
||||
fontSize,
|
||||
fontSizeIcon,
|
||||
controlItemBgHover,
|
||||
paddingXXS,
|
||||
borderRadiusLG,
|
||||
} = token;
|
||||
|
||||
const floatButtonToken = mergeToken<FloatButtonToken>(token, {
|
||||
floatButtonBackgroundColor: colorBgElevated,
|
||||
floatButtonColor: colorTextLightSolid,
|
||||
@ -323,10 +374,16 @@ export default genComponentStyleHook<'FloatButton'>('FloatButton', (token) => {
|
||||
floatButtonFontSize: fontSize,
|
||||
floatButtonIconSize: fontSizeIcon * 1.5,
|
||||
floatButtonSize: controlHeightLG,
|
||||
|
||||
floatButtonInsetBlockEnd: marginXXL,
|
||||
floatButtonInsetInlineEnd: marginLG,
|
||||
floatButtonBodySize: controlHeightLG - paddingXXS * 2,
|
||||
// 这里的 paddingXXS 是简写,完整逻辑是 (controlHeightLG - (controlHeightLG - paddingXXS * 2)) / 2,
|
||||
floatButtonBodyPadding: paddingXXS,
|
||||
badgeOffset: paddingXXS * 1.5,
|
||||
dotOffsetInCircle: getOffset(controlHeightLG / 2),
|
||||
dotOffsetInSquare: getOffset(borderRadiusLG),
|
||||
});
|
||||
|
||||
return [
|
||||
floatButtonGroupStyle(floatButtonToken),
|
||||
sharedFloatButtonStyle(floatButtonToken),
|
||||
|
10
components/float-button/util.ts
Normal file
10
components/float-button/util.ts
Normal file
@ -0,0 +1,10 @@
|
||||
const getOffset = (radius: number): number => {
|
||||
if (radius === 0) {
|
||||
return 0;
|
||||
}
|
||||
// 如果要考虑通用性,这里应该用三角函数 Math.sin(45)
|
||||
// 但是这个场景比较特殊,始终是等腰直角三角形,所以直接用 Math.sqrt() 开方即可
|
||||
return radius - Math.sqrt(radius ** 2 / 2);
|
||||
};
|
||||
|
||||
export default getOffset;
|
Loading…
Reference in New Issue
Block a user