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:
lijianan 2023-03-20 10:28:20 +08:00 committed by GitHub
parent f06220d756
commit 51b973d9d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3530 additions and 2138 deletions

View File

@ -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>
);

View File

@ -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>
`;

View File

@ -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>
`;

View File

@ -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);
});
});

View File

@ -0,0 +1,7 @@
## zh-CN
调试使用。
## en-US
debug use.

View 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;

View File

@ -0,0 +1,7 @@
## zh-CN
右上角附带圆形徽标数字的悬浮按钮。
## en-US
FloatButton with Badge.

View 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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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>;
}

View File

@ -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),

View 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;