generated from arvinxx/npm-template
-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ feat: 添加 BackToBottom 按钮的各种配置透出 (#69)
* ✨ feat: add backtobottom config
- Loading branch information
1 parent
d89867d
commit 7edb085
Showing
7 changed files
with
199 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,112 +1,139 @@ | ||
import { Button, type BackTopProps } from 'antd'; | ||
import { ListEnd } from 'lucide-react'; | ||
import { | ||
MouseEventHandler, | ||
memo, | ||
useEffect, | ||
useMemo, | ||
useRef, | ||
useState, | ||
type CSSProperties, | ||
} from 'react'; | ||
import { MouseEventHandler, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'; | ||
|
||
import Icon from '@/Icon'; | ||
import { useStyles } from './style'; | ||
|
||
export interface BackBottomProps { | ||
className?: string; | ||
/** | ||
* @description | ||
* 点击的回调 | ||
*/ | ||
onClick?: BackTopProps['onClick']; | ||
style?: CSSProperties; | ||
target: React.RefObject<HTMLDivElement>; | ||
text?: string; | ||
visibilityHeight?: BackTopProps['visibilityHeight']; | ||
/** | ||
* @description 自定义渲染 dom | ||
* @param defaultDom | ||
* @param scrollToBottom | ||
* @param BackBottomConfig | ||
* @returns React.ReactNode | ||
*/ | ||
render?: ( | ||
defaultDom: React.ReactNode, | ||
scrollToBottom: MouseEventHandler<HTMLDivElement>, | ||
BackBottomConfig: BackBottomProps, | ||
) => React.ReactNode; | ||
/** | ||
* @description | ||
* 是否一直显示 | ||
*/ | ||
alwaysShow?: boolean; | ||
} | ||
|
||
const BackBottom = memo<BackBottomProps>( | ||
({ visibilityHeight = 240, target, onClick, style, className, text }) => { | ||
const [visible, setVisible] = useState<boolean>(false); | ||
const { styles, cx } = useStyles(visible); | ||
const ref = useRef<HTMLAnchorElement | HTMLButtonElement>(null); | ||
const BackBottom = (props: BackBottomProps) => { | ||
const { | ||
visibilityHeight = 240, | ||
target, | ||
onClick, | ||
style, | ||
className, | ||
text, | ||
render, | ||
alwaysShow = false, | ||
} = props || {}; | ||
const [visible, setVisible] = useState<boolean>(alwaysShow); | ||
const { styles, cx } = useStyles(visible); | ||
const ref = useRef<HTMLAnchorElement | HTMLButtonElement>(null); | ||
|
||
const [isWindowAvailable, setIsWindowAvailable] = useState(false); | ||
const [isWindowAvailable, setIsWindowAvailable] = useState(false); | ||
|
||
useEffect(() => { | ||
// 检查window对象是否已经可用 | ||
if (typeof window !== 'undefined') { | ||
setIsWindowAvailable(true); | ||
} | ||
}, []); | ||
useEffect(() => { | ||
// 检查window对象是否已经可用 | ||
if (typeof window !== 'undefined') { | ||
setIsWindowAvailable(true); | ||
} | ||
}, []); | ||
|
||
const current = useMemo(() => { | ||
if (target.current && target.current.scrollHeight > target.current.clientHeight) { | ||
return target.current; | ||
} | ||
return document.body; | ||
}, [isWindowAvailable]); | ||
const current = useMemo(() => { | ||
if (target.current && target.current.scrollHeight > target.current.clientHeight) { | ||
return target.current; | ||
} | ||
return document.body; | ||
}, [isWindowAvailable]); | ||
|
||
const scrollHeight = current?.scrollHeight || 0; | ||
const clientHeight = current?.clientHeight || 0; | ||
const [scroll, setScroll] = useState({ top: 0, left: 0 }); | ||
const scrollHeight = current?.scrollHeight || 0; | ||
const clientHeight = current?.clientHeight || 0; | ||
const [scroll, setScroll] = useState({ top: 0, left: 0 }); | ||
|
||
const timeRef = useRef<number | null>(null); | ||
const timeRef = useRef<number | null>(null); | ||
|
||
useEffect(() => { | ||
if (typeof window === 'undefined') return; | ||
if (typeof current === 'undefined') return; | ||
const scroll = () => { | ||
timeRef.current = window.setTimeout(() => { | ||
useEffect(() => { | ||
if (typeof window === 'undefined') return; | ||
if (typeof current === 'undefined') return; | ||
const scroll = () => { | ||
timeRef.current = window.setTimeout(() => { | ||
if (!alwaysShow) { | ||
setVisible(current?.scrollTop + clientHeight + visibilityHeight < scrollHeight); | ||
setScroll({ | ||
top: current?.scrollTop, | ||
left: current?.scrollLeft, | ||
}); | ||
}, 60); | ||
}; | ||
current?.addEventListener?.('scroll', scroll, { | ||
passive: true, | ||
}); | ||
return () => { | ||
if (timeRef.current) { | ||
clearTimeout(timeRef.current); | ||
} | ||
current?.removeEventListener?.('scroll', scroll); | ||
}; | ||
}, [current]); | ||
|
||
useEffect(() => { | ||
if (scroll?.top) { | ||
setVisible(scroll?.top + clientHeight + visibilityHeight < scrollHeight); | ||
setScroll({ | ||
top: current?.scrollTop, | ||
left: current?.scrollLeft, | ||
}); | ||
}, 60); | ||
}; | ||
current?.addEventListener?.('scroll', scroll, { | ||
passive: true, | ||
}); | ||
return () => { | ||
if (timeRef.current) { | ||
clearTimeout(timeRef.current); | ||
} | ||
}, [scrollHeight, scroll, visibilityHeight, current]); | ||
current?.removeEventListener?.('scroll', scroll); | ||
}; | ||
}, [current]); | ||
|
||
const scrollToBottom: MouseEventHandler<HTMLDivElement> = (e) => { | ||
useEffect(() => { | ||
if (scroll?.top && !alwaysShow) { | ||
setVisible(scroll?.top + clientHeight + visibilityHeight < scrollHeight); | ||
} | ||
}, [scrollHeight, scroll, visibilityHeight, current]); | ||
|
||
const scrollToBottom: MouseEventHandler<HTMLDivElement> = (e) => { | ||
(target as any)?.current?.scrollTo({ behavior: 'smooth', left: 0, top: scrollHeight }); | ||
onClick?.(e); | ||
}; | ||
|
||
/** | ||
* @description | ||
* 为了解决在使用了 ProChatProvider 的情况下,BackBottom 无法正常工作的问题 | ||
*/ | ||
useEffect(() => { | ||
setTimeout(() => { | ||
(target as any)?.current?.scrollTo({ behavior: 'smooth', left: 0, top: scrollHeight }); | ||
onClick?.(e); | ||
}; | ||
}, 16); | ||
}, []); | ||
|
||
const defauleDom = ( | ||
<Button | ||
className={cx(styles, className)} | ||
icon={<Icon icon={ListEnd} />} | ||
onClick={scrollToBottom} | ||
ref={ref} | ||
size={'small'} | ||
style={{ bottom: 18, position: 'absolute', right: 16, ...style }} | ||
> | ||
{text || 'Back to bottom'} | ||
</Button> | ||
); | ||
|
||
if (render) return render(defauleDom, scrollToBottom, props); | ||
|
||
/** | ||
* @description | ||
* 为了解决在使用了 ProChatProvider 的情况下,BackBottom 无法正常工作的问题 | ||
*/ | ||
useEffect(() => { | ||
setTimeout(() => { | ||
(target as any)?.current?.scrollTo({ behavior: 'smooth', left: 0, top: scrollHeight }); | ||
}, 16); | ||
}, []); | ||
|
||
return ( | ||
<Button | ||
className={cx(styles, className)} | ||
icon={<Icon icon={ListEnd} />} | ||
onClick={scrollToBottom} | ||
ref={ref} | ||
size={'small'} | ||
style={{ bottom: 16, position: 'absolute', right: 16, ...style }} | ||
> | ||
{text || 'Back to bottom'} | ||
</Button> | ||
); | ||
}, | ||
); | ||
return defauleDom; | ||
}; | ||
|
||
export default BackBottom; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* compact: true | ||
*/ | ||
import { ProChat } from '@ant-design/pro-chat'; | ||
import { useTheme } from 'antd-style'; | ||
|
||
import { Button } from 'antd'; | ||
import { example } from '../mocks/fullFeature'; | ||
|
||
export default () => { | ||
const theme = useTheme(); | ||
return ( | ||
<> | ||
<div style={{ background: theme.colorBgLayout, height: '800px' }}> | ||
<ProChat | ||
displayMode={'docs'} | ||
style={{ height: '100%' }} | ||
chats={example.chats} | ||
config={example.config} | ||
backtoBottomConfig={{ | ||
render: (_, scrollToBottom) => { | ||
return ( | ||
<Button | ||
type="primary" | ||
onClick={scrollToBottom} | ||
style={{ | ||
alignSelf: 'flex-end', | ||
width: '200px', | ||
marginRight: '18px', | ||
}} | ||
> | ||
Scroll To Bottom | ||
</Button> | ||
); | ||
}, | ||
}} | ||
/> | ||
</div> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.