Skip to content

Commit 4c36143

Browse files
committed
feat(ui): fab add backtop support
1 parent 308bdf0 commit 4c36143

10 files changed

Lines changed: 235 additions & 38 deletions

File tree

packages/ui/src/components/fab/Fab.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getClassName } from '@react-devui/utils';
77

88
import { usePrefixConfig, useComponentConfig, useDValue } from '../../hooks';
99
import { registerComponentMate } from '../../utils';
10+
import { DFabBacktop } from './FabBacktop';
1011
import { DFabButton } from './FabButton';
1112

1213
export interface DFabProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
@@ -20,6 +21,7 @@ const { COMPONENT_NAME } = registerComponentMate({ COMPONENT_NAME: 'DFab' as con
2021
export const DFab: {
2122
(props: DFabProps): JSX.Element | null;
2223
Button: typeof DFabButton;
24+
Backtop: typeof DFabBacktop;
2325
} = (props) => {
2426
const {
2527
children,
@@ -75,3 +77,4 @@ export const DFab: {
7577
};
7678

7779
DFab.Button = DFabButton;
80+
DFab.Backtop = DFabBacktop;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type { DTransitionState } from '../_transition';
2+
import type { DFabButtonProps } from './FabButton';
3+
import type { DElementSelector } from '@react-devui/hooks/useElement';
4+
5+
import React, { useRef, useState } from 'react';
6+
7+
import { useElement, useEvent, useIsomorphicLayoutEffect, useResize } from '@react-devui/hooks';
8+
import { VerticalAlignTopOutlined } from '@react-devui/icons';
9+
import { checkNodeExist, scrollTo } from '@react-devui/utils';
10+
11+
import { useComponentConfig, useLayout } from '../../hooks';
12+
import { registerComponentMate, TTANSITION_DURING_BASE } from '../../utils';
13+
import { DTransition } from '../_transition';
14+
import { DFabButton } from './FabButton';
15+
16+
export interface DFabBacktopProps extends DFabButtonProps {
17+
dPage?: DElementSelector;
18+
dDistance?: number;
19+
dScrollBehavior?: 'instant' | 'smooth';
20+
}
21+
22+
const { COMPONENT_NAME } = registerComponentMate({ COMPONENT_NAME: 'DFab.Backtop' as const });
23+
function FabBacktop(props: DFabBacktopProps, ref: React.ForwardedRef<HTMLButtonElement>): JSX.Element | null {
24+
const {
25+
children,
26+
dPage,
27+
dDistance = 400,
28+
dScrollBehavior = 'instant',
29+
30+
...restProps
31+
} = useComponentConfig(COMPONENT_NAME, props);
32+
33+
//#region Context
34+
const { dScrollEl, dResizeEl } = useLayout();
35+
//#endregion
36+
37+
const dataRef = useRef<{
38+
clearTid?: () => void;
39+
}>({});
40+
41+
const pageEl = useElement(dPage ?? dScrollEl);
42+
const resizeEl = useElement(dResizeEl);
43+
44+
const [visible, setVisible] = useState(false);
45+
46+
const transitionStyles: Partial<Record<DTransitionState, React.CSSProperties>> = {
47+
enter: { opacity: 0 },
48+
entering: {
49+
transition: ['opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms linear`).join(', '),
50+
},
51+
leaving: {
52+
opacity: 0,
53+
transition: ['opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms linear`).join(', '),
54+
},
55+
leaved: { display: 'none' },
56+
};
57+
58+
const updateBackTop = () => {
59+
if (!pageEl) {
60+
return;
61+
}
62+
63+
setVisible(pageEl.scrollTop >= dDistance);
64+
};
65+
useIsomorphicLayoutEffect(() => {
66+
updateBackTop();
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
}, []);
69+
70+
useResize(resizeEl, updateBackTop);
71+
useEvent(pageEl, 'scroll', updateBackTop, { passive: true });
72+
73+
return (
74+
<DTransition dIn={visible} dDuring={TTANSITION_DURING_BASE}>
75+
{(state) => (
76+
<DFabButton
77+
{...restProps}
78+
ref={ref}
79+
style={{
80+
...restProps.style,
81+
...transitionStyles[state],
82+
}}
83+
onClick={(e) => {
84+
restProps.onClick?.(e);
85+
86+
if (pageEl) {
87+
dataRef.current.clearTid?.();
88+
dataRef.current.clearTid = scrollTo(pageEl, {
89+
top: 0,
90+
behavior: dScrollBehavior,
91+
});
92+
}
93+
}}
94+
>
95+
{checkNodeExist(children) ? children : <VerticalAlignTopOutlined />}
96+
</DFabButton>
97+
)}
98+
</DTransition>
99+
);
100+
}
101+
102+
export const DFabBacktop = React.forwardRef(FabBacktop);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
title:
3+
en-US: Loading
4+
zh-CN: 加载中
5+
---
6+
7+
# en-US
8+
9+
Add the `dLoading` attribute to make the button in the loading state.
10+
11+
# zh-CN
12+
13+
添加 `dLoading` 属性即可让按钮处于加载状态。
14+
15+
```tsx
16+
import { useState, useRef } from 'react';
17+
18+
import { useAsync, useImmer } from '@react-devui/hooks';
19+
import { CaretRightOutlined, DeleteOutlined } from '@react-devui/icons';
20+
import { DFab } from '@react-devui/ui';
21+
22+
export default function Demo() {
23+
const dataRef = useRef({});
24+
const async = useAsync();
25+
26+
const [loading, setLoading] = useImmer(false);
27+
const [expand, setExpand] = useImmer(false);
28+
29+
return (
30+
<DFab
31+
dList={[
32+
{
33+
placement: 'right',
34+
actions: [
35+
<DFab.Button
36+
dTheme="danger"
37+
dVariant="circle"
38+
dLoading={loading}
39+
onClick={() => {
40+
setLoading(true);
41+
dataRef.current.clearTid?.();
42+
dataRef.current.clearTid = async.setTimeout(() => {
43+
setLoading(false);
44+
setExpand(false);
45+
}, 1000);
46+
}}
47+
>
48+
<DeleteOutlined />
49+
</DFab.Button>,
50+
],
51+
},
52+
]}
53+
dExpand={expand}
54+
>
55+
<DFab.Button dVariant="circle" onClick={() => setExpand((draft) => !draft)}>
56+
<CaretRightOutlined />
57+
</DFab.Button>
58+
</DFab>
59+
);
60+
}
61+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title:
3+
en-US: Backtop
4+
zh-CN: 回到顶部
5+
---
6+
7+
# en-US
8+
9+
`DFab.Backtop` component provided.
10+
11+
# zh-CN
12+
13+
提供了 `DFab.Backtop` 组件。
14+
15+
```tsx
16+
import { DFab } from '@react-devui/ui';
17+
18+
export default function Demo() {
19+
return (
20+
<DFab style={{ position: 'fixed', right: 100, bottom: 40 }}>
21+
<DFab.Backtop dVariant="circle" />
22+
</DFab>
23+
);
24+
}
25+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './Fab';
22
export * from './FabButton';
3+
export * from './FabBacktop';

packages/ui/src/components/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ export { DDropdown } from './dropdown';
4949
export type { DEmptyProps } from './empty';
5050
export { DEmpty } from './empty';
5151

52-
export type { DFabProps, DFabButtonProps } from './fab';
53-
export { DFab, DFabButton } from './fab';
52+
export type { DFabProps, DFabButtonProps, DFabBacktopProps } from './fab';
53+
export { DFab, DFabButton, DFabBacktop } from './fab';
5454

5555
export type { DFormProps, DFormItemProps, DFormGroupProps } from './form';
5656
export { DForm, useForm, FormControl, FormGroup, Validators } from './form';

packages/ui/src/hooks/d-config/contex.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
DEmptyProps,
2525
DFabProps,
2626
DFabButtonProps,
27+
DFabBacktopProps,
2728
DFormProps,
2829
DFormGroupProps,
2930
DFormItemProps,
@@ -113,6 +114,7 @@ export type DComponentConfig = {
113114
DEmpty: DEmptyProps;
114115
DFab: DFabProps;
115116
'DFab.Button': DFabButtonProps;
117+
'DFab.Backtop': DFabBacktopProps;
116118
DForm: DFormProps;
117119
'DForm.Group': DFormGroupProps;
118120
'DForm.Item': DFormItemProps<any>;

packages/ui/src/styles/_variables.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ $popover-space: 10px;
9494
/** component **/
9595
--#{$variable-prefix}avatar-background-color: #bdbdbd;
9696
--#{$variable-prefix}breadcrumb-text-color: #b1b1b1;
97+
--#{$variable-prefix}fab-shadow: 0 3px 5px -1px rgb(0 0 0 / 20%), 0 6px 10px 0 rgb(0 0 0 / 14%), 0 1px 14px 0 rgb(0 0 0 / 12%);
9798
--#{$variable-prefix}mask-background-color: rgb(0 0 0 / 20%);
9899
--#{$variable-prefix}skeleton-background-color-wave: rgb(255 255 255 / 50%);
99100
--#{$variable-prefix}switch-background-color: #d4d6d9;

0 commit comments

Comments
 (0)