feat: FloatButton support placement (#50407)

* feat: 不要合并

* fix: fix

* fix: fix

* test: fix test

* test: fix test

* test: fix test

* test: fix test

* Update components/float-button/FloatButtonGroup.tsx

Co-authored-by: MadCcc <madccc@foxmail.com>
Signed-off-by: lijianan <574980606@qq.com>

* Update components/float-button/__tests__/__snapshots__/demo-extend.test.ts.snap

Co-authored-by: afc163 <afc163@gmail.com>
Signed-off-by: lijianan <574980606@qq.com>

* fix: fix

* fix: fix

* test: add test case

* fix: fix

* fix: fix

* site: update site style

* demo: update demo

---------

Signed-off-by: lijianan <574980606@qq.com>
Co-authored-by: MadCcc <madccc@foxmail.com>
Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
lijianan 2024-08-20 10:16:07 +08:00 committed by GitHub
parent a33a09ee23
commit c8413cc78f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 689 additions and 93 deletions

View File

@ -1,7 +1,6 @@
import React from 'react';
import { BgColorsOutlined, SmileOutlined } from '@ant-design/icons';
import { FloatButton } from 'antd';
import { useTheme } from 'antd-style';
import { CompactTheme, DarkTheme } from 'antd-token-previewer/es/icons';
// import { Motion } from 'antd-token-previewer/es/icons';
import { FormattedMessage, useLocation } from 'dumi';
@ -20,7 +19,6 @@ export interface ThemeSwitchProps {
const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
const { value = ['light'], onChange } = props;
const token = useTheme();
const { pathname, search } = useLocation();
// const isMotionOff = value.includes('motion-off');
@ -38,7 +36,7 @@ const ThemeSwitch: React.FC<ThemeSwitchProps> = (props) => {
>
<Link
to={getLocalizedPathname('/theme-editor', isZhCN(pathname), search)}
style={{ display: 'block', marginBottom: token.margin }}
style={{ display: 'block' }}
>
<FloatButton
icon={<BgColorsOutlined />}

View File

@ -22,6 +22,7 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
style,
shape = 'circle',
type = 'default',
placement = 'top',
icon = <FileTextOutlined />,
closeIcon,
description,
@ -40,12 +41,17 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls);
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const groupPrefixCls = `${prefixCls}-group`;
const isMenuMode = trigger && ['click', 'hover'].includes(trigger);
const isValidPlacement = placement && ['top', 'left', 'right', 'bottom'].includes(placement);
const groupCls = classNames(groupPrefixCls, hashId, cssVarCls, rootCls, className, {
[`${groupPrefixCls}-rtl`]: direction === 'rtl',
[`${groupPrefixCls}-${shape}`]: shape,
[`${groupPrefixCls}-${shape}-shadow`]: !trigger,
[`${groupPrefixCls}-${shape}-shadow`]: !isMenuMode,
[`${groupPrefixCls}-${placement}`]: isMenuMode && isValidPlacement, // 只有菜单模式才支持弹出方向
});
// ============================ zIndex ============================
@ -119,7 +125,7 @@ const FloatButtonGroup: React.FC<FloatButtonGroupProps> = (props) => {
return wrapCSSVar(
<FloatButtonGroupProvider value={shape}>
<div ref={floatButtonGroupRef} className={groupCls} style={mergedStyle} {...hoverAction}>
{trigger && ['click', 'hover'].includes(trigger) ? (
{isMenuMode ? (
<>
<CSSMotion visible={open} motionName={`${groupPrefixCls}-wrap`}>
{({ className: motionClassName }) => (

View File

@ -574,7 +574,7 @@ Array [
</span>
</button>,
<div
class="ant-float-btn-group ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="inset-inline-end: 24px;"
>
<div
@ -737,7 +737,7 @@ Array [
</button>
</div>,
<div
class="ant-float-btn-group ant-float-btn-group-square"
class="ant-float-btn-group ant-float-btn-group-square ant-float-btn-group-top"
style="inset-inline-end: 88px;"
>
<div
@ -1292,7 +1292,7 @@ exports[`renders components/float-button/demo/group.tsx extend context correctly
exports[`renders components/float-button/demo/group-menu.tsx extend context correctly 1`] = `
Array [
<div
class="ant-float-btn-group ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="inset-inline-end: 24px;"
>
<button
@ -1333,7 +1333,7 @@ Array [
</button>
</div>,
<div
class="ant-float-btn-group ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="inset-inline-end: 94px;"
>
<button
@ -1378,6 +1378,184 @@ Array [
exports[`renders components/float-button/demo/group-menu.tsx extend context correctly 2`] = `[]`;
exports[`renders components/float-button/demo/placement.tsx extend context correctly 1`] = `
<div
class="ant-flex ant-flex-align-center ant-flex-justify-space-evenly"
style="width: 100%; height: 100vh; overflow: hidden; position: relative;"
>
<div
style="width: 100px; height: 100px; position: relative;"
>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="position: absolute; inset-inline-end: 30px; bottom: 80px;"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="up"
class="anticon anticon-up"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-right"
style="position: absolute; inset-inline-end: -20px; bottom: 30px;"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="right"
class="anticon anticon-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-bottom"
style="position: absolute; inset-inline-end: 30px; bottom: -20px;"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="down"
class="anticon anticon-down"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-left"
style="position: absolute; inset-inline-end: 80px; bottom: 30px;"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
</div>
</div>
`;
exports[`renders components/float-button/demo/placement.tsx extend context correctly 2`] = `[]`;
exports[`renders components/float-button/demo/render-panel.tsx extend context correctly 1`] = `
<div
style="display: flex; column-gap: 16px; align-items: center;"
@ -1614,7 +1792,7 @@ exports[`renders components/float-button/demo/render-panel.tsx extend context co
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-pure ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-pure ant-float-btn-group-circle ant-float-btn-group-top"
>
<div
class="ant-float-btn-group-wrap-appear ant-float-btn-group-wrap-appear-start ant-float-btn-group-wrap ant-float-btn-group-wrap"

View File

@ -526,7 +526,7 @@ Array [
</span>
</button>,
<div
class="ant-float-btn-group ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="inset-inline-end:24px"
>
<div
@ -689,7 +689,7 @@ Array [
</button>
</div>,
<div
class="ant-float-btn-group ant-float-btn-group-square"
class="ant-float-btn-group ant-float-btn-group-square ant-float-btn-group-top"
style="inset-inline-end:88px"
>
<div
@ -1238,7 +1238,7 @@ Array [
exports[`renders components/float-button/demo/group-menu.tsx correctly 1`] = `
Array [
<div
class="ant-float-btn-group ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="inset-inline-end:24px"
>
<button
@ -1279,7 +1279,7 @@ Array [
</button>
</div>,
<div
class="ant-float-btn-group ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="inset-inline-end:94px"
>
<button
@ -1322,6 +1322,182 @@ Array [
]
`;
exports[`renders components/float-button/demo/placement.tsx correctly 1`] = `
<div
class="ant-flex ant-flex-align-center ant-flex-justify-space-evenly"
style="width:100%;height:100vh;overflow:hidden;position:relative"
>
<div
style="width:100px;height:100px;position:relative"
>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-top"
style="position:absolute;inset-inline-end:30px;bottom:80px"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="up"
class="anticon anticon-up"
role="img"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-right"
style="position:absolute;inset-inline-end:-20px;bottom:30px"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="right"
class="anticon anticon-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-bottom"
style="position:absolute;inset-inline-end:30px;bottom:-20px"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="down"
class="anticon anticon-down"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-group-circle ant-float-btn-group-left"
style="position:absolute;inset-inline-end:80px;bottom:30px"
>
<button
class="ant-float-btn ant-float-btn-group-trigger ant-float-btn-default ant-float-btn-circle"
type="button"
>
<div
class="ant-float-btn-body"
>
<div
class="ant-float-btn-content"
>
<div
class="ant-float-btn-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
</div>
</div>
</button>
</div>
</div>
</div>
`;
exports[`renders components/float-button/demo/render-panel.tsx correctly 1`] = `
<div
style="display:flex;column-gap:16px;align-items:center"
@ -1558,7 +1734,7 @@ exports[`renders components/float-button/demo/render-panel.tsx correctly 1`] = `
</button>
</div>
<div
class="ant-float-btn-group ant-float-btn-pure ant-float-btn-group-circle"
class="ant-float-btn-group ant-float-btn-pure ant-float-btn-group-circle ant-float-btn-group-top"
>
<div
class="ant-float-btn-group-wrap"

View File

@ -120,4 +120,16 @@ describe('FloatButtonGroup', () => {
expect(container.querySelector('.ant-badge')).toBeTruthy();
});
it('FloatButton.Group should support placement', () => {
(['bottom', 'left', 'right', 'top'] as const).forEach((placement) => {
const { container } = render(
<FloatButton.Group placement={placement} trigger="click" open>
<FloatButton />
</FloatButton.Group>,
);
const element = container.querySelector<HTMLDivElement>('.ant-float-btn-group');
expect(element).toHaveClass(`ant-float-btn-group-${placement}`);
});
});
});

View File

@ -0,0 +1,7 @@
## zh-CN
自定义弹出位置,提供了四个预设值:`top`、`right`、`bottom`、`left`,默认值为 `top`
## en-US
Customize animation placement, providing four preset placement: `top`, `right`, `bottom`, `left`, the `top` position by default.

View File

@ -0,0 +1,74 @@
import React from 'react';
import {
CommentOutlined,
DownOutlined,
LeftOutlined,
RightOutlined,
UpOutlined,
} from '@ant-design/icons';
import { Flex, FloatButton } from 'antd';
const BOX_SIZE = 100;
const BUTTON_SIZE = 40;
const wrapperStyle: React.CSSProperties = {
width: '100%',
height: '100vh',
overflow: 'hidden',
position: 'relative',
};
const boxStyle: React.CSSProperties = {
width: BOX_SIZE,
height: BOX_SIZE,
position: 'relative',
};
const insetInlineEnd: React.CSSProperties['insetInlineEnd'][] = [
(BOX_SIZE - BUTTON_SIZE) / 2,
-(BUTTON_SIZE / 2),
(BOX_SIZE - BUTTON_SIZE) / 2,
BOX_SIZE - BUTTON_SIZE / 2,
];
const bottom: React.CSSProperties['bottom'][] = [
BOX_SIZE - BUTTON_SIZE / 2,
(BOX_SIZE - BUTTON_SIZE) / 2,
-BUTTON_SIZE / 2,
(BOX_SIZE - BUTTON_SIZE) / 2,
];
const icons = [
<UpOutlined key="up" />,
<RightOutlined key="right" />,
<DownOutlined key="down" />,
<LeftOutlined key="left" />,
];
const App: React.FC = () => (
<Flex justify="space-evenly" align="center" style={wrapperStyle}>
<div style={boxStyle}>
{(['top', 'right', 'bottom', 'left'] as const).map((placement, i) => {
const style: React.CSSProperties = {
position: 'absolute',
insetInlineEnd: insetInlineEnd[i],
bottom: bottom[i],
};
return (
<FloatButton.Group
key={placement}
trigger="click"
placement={placement}
style={style}
icon={icons[i]}
>
<FloatButton />
<FloatButton icon={<CommentOutlined />} />
</FloatButton.Group>
);
})}
</div>
</Flex>
);
export default App;

View File

@ -26,6 +26,7 @@ tag: 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/controlled.tsx" iframe="360">Controlled mode</code>
<code src="./demo/placement.tsx" iframe="380" version="5.21.0">placement</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>
@ -59,6 +60,7 @@ Common props ref[Common props](/docs/react/common-props)
| trigger | Which action can trigger menu open/close | `click` \| `hover` | - | |
| open | Whether the menu is visible or not, use it with trigger | boolean | - | |
| closeIcon | Customize close button icon | React.ReactNode | `<CloseOutlined />` | |
| placement | Customize menu animation placement | `top` \| `left` \| `right` \| `bottom` | `top` | 5.21.0 |
| onOpenChange | Callback executed when active menu is changed, use it with trigger | (open: boolean) => void | - | |
### FloatButton.BackTop

View File

@ -27,6 +27,7 @@ tag: 5.0.0
<code src="./demo/group.tsx" iframe="360">浮动按钮组</code>
<code src="./demo/group-menu.tsx" iframe="360">菜单模式</code>
<code src="./demo/controlled.tsx" iframe="360">受控模式</code>
<code src="./demo/placement.tsx" iframe="380" version="5.21.0">弹出方向</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>
@ -60,6 +61,7 @@ tag: 5.0.0
| trigger | 触发方式(有触发方式为菜单模式) | `click` \| `hover` | - | |
| open | 受控展开,需配合 trigger 一起使用 | boolean | - | |
| closeIcon | 自定义关闭按钮 | React.ReactNode | `<CloseOutlined />` | |
| placement | 自定义菜单弹出位置 | `top` \| `left` \| `right` \| `bottom` | `top` | 5.21.0 |
| onOpenChange | 展开收起时的回调,需配合 trigger 一起使用 | (open: boolean) => void | - | |
### FloatButton.BackTop

View File

@ -49,6 +49,8 @@ export interface FloatButtonGroupProps extends FloatButtonProps {
open?: boolean;
// 关闭按钮自定义图标
closeIcon?: React.ReactNode;
// 菜单弹出方向
placement?: 'top' | 'left' | 'right' | 'bottom';
// 展开收起的回调
onOpenChange?: (open: boolean) => void;
}

View File

@ -1,12 +1,12 @@
import type { CSSObject } from '@ant-design/cssinjs';
import { Keyframes, unit } from '@ant-design/cssinjs';
import { unit } from '@ant-design/cssinjs';
import { resetComponent } from '../../style';
import { initFadeMotion } from '../../style/motion/fade';
import { initMotion } from '../../style/motion/motion';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
import getOffset from '../util';
import floatButtonGroupMotion from './keyframes';
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
@ -26,7 +26,7 @@ export interface ComponentToken {
* @desc FloatButton Token
* @descEN Token for FloatButton component
*/
type FloatButtonToken = FullToken<'FloatButton'> & {
export type FloatButtonToken = FullToken<'FloatButton'> & {
/**
* @desc FloatButton
* @descEN Color of FloatButton
@ -86,58 +86,6 @@ type FloatButtonToken = FullToken<'FloatButton'> & {
floatButtonInsetInlineEnd: number;
};
const initFloatButtonGroupMotion = (token: FloatButtonToken) => {
const { componentCls, floatButtonSize, motionDurationSlow, motionEaseInOutCirc } = token;
const groupPrefixCls = `${componentCls}-group`;
const moveDownIn = new Keyframes('antFloatButtonMoveDownIn', {
'0%': {
transform: `translate3d(0, ${unit(floatButtonSize)}, 0)`,
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, ${unit(floatButtonSize)}, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
});
return [
{
[`${groupPrefixCls}-wrap`]: {
...initMotion(`${groupPrefixCls}-wrap`, moveDownIn, moveDownOut, motionDurationSlow, true),
},
},
{
[`${groupPrefixCls}-wrap`]: {
[`
&${groupPrefixCls}-wrap-enter,
&${groupPrefixCls}-wrap-appear
`]: {
opacity: 0,
animationTimingFunction: motionEaseInOutCirc,
},
[`&${groupPrefixCls}-wrap-leave`]: {
animationTimingFunction: motionEaseInOutCirc,
},
},
},
];
};
// ============================== Group ==============================
const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token) => {
const {
@ -157,22 +105,25 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
[groupPrefixCls]: {
...resetComponent(token),
zIndex: zIndexPopupBase,
display: 'block',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
border: 'none',
position: 'fixed',
width: floatButtonSize,
height: 'auto',
boxShadow: 'none',
minWidth: floatButtonSize,
minHeight: floatButtonSize,
insetInlineEnd: token.floatButtonInsetInlineEnd,
insetBlockEnd: token.floatButtonInsetBlockEnd,
bottom: token.floatButtonInsetBlockEnd,
borderRadius: borderRadiusLG,
[`${groupPrefixCls}-wrap`]: {
zIndex: -1,
display: 'block',
position: 'relative',
marginBottom: margin,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
},
[`&${groupPrefixCls}-rtl`]: {
direction: 'rtl',
@ -181,14 +132,30 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
position: 'static',
},
},
[`${groupPrefixCls}-top > ${groupPrefixCls}-wrap`]: {
flexDirection: 'column',
top: 'auto',
bottom: calc(floatButtonSize).add(margin).equal(),
},
[`${groupPrefixCls}-bottom > ${groupPrefixCls}-wrap`]: {
flexDirection: 'column',
top: calc(floatButtonSize).add(margin).equal(),
bottom: 'auto',
},
[`${groupPrefixCls}-right > ${groupPrefixCls}-wrap`]: {
flexDirection: 'row',
left: { _skip_check_: true, value: calc(floatButtonSize).add(margin).equal() },
right: { _skip_check_: true, value: 'auto' },
},
[`${groupPrefixCls}-left > ${groupPrefixCls}-wrap`]: {
flexDirection: 'row',
left: { _skip_check_: true, value: 'auto' },
right: { _skip_check_: true, value: calc(floatButtonSize).add(margin).equal() },
},
[`${groupPrefixCls}-circle`]: {
[`${componentCls}-circle:not(:last-child)`]: {
marginBottom: token.margin,
[`${componentCls}-body`]: {
width: floatButtonSize,
height: floatButtonSize,
borderRadius: '50%',
},
gap: margin,
[`${groupPrefixCls}-wrap`]: {
gap: margin,
},
},
[`${groupPrefixCls}-square`]: {
@ -217,14 +184,22 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
},
},
[`${groupPrefixCls}-wrap`]: {
display: 'block',
borderRadius: borderRadiusLG,
boxShadow: token.boxShadowSecondary,
[`${componentCls}-square`]: {
boxShadow: 'none',
marginTop: 0,
borderRadius: 0,
padding: floatButtonBodyPadding,
[`${componentCls}-body`]: {
width: token.floatButtonBodySize,
height: token.floatButtonBodySize,
},
},
},
},
[`${groupPrefixCls}-top > ${groupPrefixCls}-wrap, ${groupPrefixCls}-bottom > ${groupPrefixCls}-wrap`]:
{
[`> ${componentCls}-square`]: {
'&:first-child': {
borderStartStartRadius: borderRadiusLG,
borderStartEndRadius: borderRadiusLG,
@ -236,13 +211,25 @@ const floatButtonGroupStyle: GenerateStyle<FloatButtonToken, CSSObject> = (token
'&:not(:last-child)': {
borderBottom: `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}`,
},
[`${componentCls}-body`]: {
width: token.floatButtonBodySize,
height: token.floatButtonBodySize,
},
},
[`${groupPrefixCls}-left > ${groupPrefixCls}-wrap, ${groupPrefixCls}-right > ${groupPrefixCls}-wrap`]:
{
[`> ${componentCls}-square`]: {
'&:first-child': {
borderStartStartRadius: borderRadiusLG,
borderEndStartRadius: borderRadiusLG,
},
'&:last-child': {
borderStartEndRadius: borderRadiusLG,
borderEndEndRadius: borderRadiusLG,
},
'&:not(:last-child)': {
borderInlineEnd: `${unit(token.lineWidth)} ${token.lineType} ${token.colorSplit}`,
},
},
},
},
[`${groupPrefixCls}-circle-shadow`]: {
boxShadow: 'none',
},
@ -290,7 +277,7 @@ const sharedFloatButtonStyle: GenerateStyle<FloatButtonToken, CSSObject> = (toke
width: floatButtonSize,
height: floatButtonSize,
insetInlineEnd: token.floatButtonInsetInlineEnd,
insetBlockEnd: token.floatButtonInsetBlockEnd,
bottom: token.floatButtonInsetBlockEnd,
boxShadow: token.boxShadowSecondary,
// Pure Panel
'&-pure': {
@ -454,12 +441,11 @@ export default genStyleHooks(
floatButtonBodyPadding: paddingXXS,
badgeOffset: calc(paddingXXS).mul(1.5).equal(),
});
return [
floatButtonGroupStyle(floatButtonToken),
sharedFloatButtonStyle(floatButtonToken),
initFadeMotion(token),
initFloatButtonGroupMotion(floatButtonToken),
floatButtonGroupMotion(floatButtonToken),
];
},
prepareComponentToken,

View File

@ -0,0 +1,153 @@
import { Keyframes, unit } from '@ant-design/cssinjs';
import type { FloatButtonToken } from '.';
import { initMotion } from '../../style/motion/motion';
const floatButtonGroupMotion = (token: FloatButtonToken) => {
const { componentCls, floatButtonSize, motionDurationSlow, motionEaseInOutCirc, calc } = token;
const moveTopIn = new Keyframes('antFloatButtonMoveTopIn', {
'0%': {
transform: `translate3d(0, ${unit(floatButtonSize)}, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
const moveTopOut = new Keyframes('antFloatButtonMoveTopOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: `translate3d(0, ${unit(floatButtonSize)}, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
});
const moveRightIn = new Keyframes('antFloatButtonMoveRightIn', {
'0%': {
transform: `translate3d(${calc(floatButtonSize).mul(-1).equal()}, 0, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
const moveRightOut = new Keyframes('antFloatButtonMoveRightOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: `translate3d(${calc(floatButtonSize).mul(-1).equal()}, 0, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
});
const moveBottomIn = new Keyframes('antFloatButtonMoveBottomIn', {
'0%': {
transform: `translate3d(0, ${calc(floatButtonSize).mul(-1).equal()}, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
const moveBottomOut = new Keyframes('antFloatButtonMoveBottomOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: `translate3d(0, ${calc(floatButtonSize).mul(-1).equal()}, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
});
const moveLeftIn = new Keyframes('antFloatButtonMoveLeftIn', {
'0%': {
transform: `translate3d(${unit(floatButtonSize)}, 0, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
});
const moveLeftOut = new Keyframes('antFloatButtonMoveLeftOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1,
},
'100%': {
transform: `translate3d(${unit(floatButtonSize)}, 0, 0)`,
transformOrigin: '0 0',
opacity: 0,
},
});
const groupPrefixCls = `${componentCls}-group`;
return [
{
[groupPrefixCls]: {
[`&${groupPrefixCls}-top ${groupPrefixCls}-wrap`]: initMotion(
`${groupPrefixCls}-wrap`,
moveTopIn,
moveTopOut,
motionDurationSlow,
true,
),
[`&${groupPrefixCls}-bottom ${groupPrefixCls}-wrap`]: initMotion(
`${groupPrefixCls}-wrap`,
moveBottomIn,
moveBottomOut,
motionDurationSlow,
true,
),
[`&${groupPrefixCls}-left ${groupPrefixCls}-wrap`]: initMotion(
`${groupPrefixCls}-wrap`,
moveLeftIn,
moveLeftOut,
motionDurationSlow,
true,
),
[`&${groupPrefixCls}-right ${groupPrefixCls}-wrap`]: initMotion(
`${groupPrefixCls}-wrap`,
moveRightIn,
moveRightOut,
motionDurationSlow,
true,
),
},
},
{
[`${groupPrefixCls}-wrap`]: {
[`&${groupPrefixCls}-wrap-enter, &${groupPrefixCls}-wrap-appear`]: {
opacity: 0,
animationTimingFunction: motionEaseInOutCirc,
},
[`&${groupPrefixCls}-wrap-leave`]: {
opacity: 1,
animationTimingFunction: motionEaseInOutCirc,
},
},
},
];
};
export default floatButtonGroupMotion;