mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
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:
parent
a33a09ee23
commit
c8413cc78f
@ -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 />}
|
||||
|
@ -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 }) => (
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
7
components/float-button/demo/placement.md
Normal file
7
components/float-button/demo/placement.md
Normal 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.
|
74
components/float-button/demo/placement.tsx
Normal file
74
components/float-button/demo/placement.tsx
Normal 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;
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -49,6 +49,8 @@ export interface FloatButtonGroupProps extends FloatButtonProps {
|
||||
open?: boolean;
|
||||
// 关闭按钮自定义图标
|
||||
closeIcon?: React.ReactNode;
|
||||
// 菜单弹出方向
|
||||
placement?: 'top' | 'left' | 'right' | 'bottom';
|
||||
// 展开收起的回调
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
@ -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,
|
||||
|
153
components/float-button/style/keyframes.ts
Normal file
153
components/float-button/style/keyframes.ts
Normal 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;
|
Loading…
Reference in New Issue
Block a user