-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(SideBar): 升级SideBar样式 * feat(SideBar): 创建SideBar组件 * feat(SideBar): 创建SideBar文档 * fix(Grid): 补充Grid遗漏的type * chore: update snap * test(SideBar): 测试增加React路由 * test(SideBar): update snap * docs(SideBar): 解决演示用例样式冲突问题 * test(SideBar): update snap * docs(SideBar): 修复示例内容体高度样式 * refactor(SideBar): 按规范改用useDefaultProps、usePrefixClass * docs(SideBar): 移除多余API文档 * chore(SideBar): update tdesign-api * test: update snap * docs(SideBar): 删除多余示例参数 * chore(SideBar): update tdesign-api * chore: 变更common分支 * chore(SideBar): update tdesign-api * chore(SideBar): update tdesign-api * chore(SideBar): update api * perf(Demo): 示例头部的返回按钮优化为按需引入 * chore: update snapshot --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
8a52e88
commit 2cb1212
Showing
29 changed files
with
6,581 additions
and
253 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
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
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,88 @@ | ||
import React, { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; | ||
import type { StyledProps } from '../common'; | ||
import type { TdSideBarProps } from './type'; | ||
import { sideBarDefaultProps } from './defaultProps'; | ||
import { SideBarProvider } from './SideBarContext'; | ||
import useDefault from '../_util/useDefault'; | ||
import parseTNode from '../_util/parseTNode'; | ||
import useDefaultProps from '../hooks/useDefaultProps'; | ||
import { usePrefixClass } from '../hooks/useClass'; | ||
|
||
export interface SideBarProps extends TdSideBarProps, StyledProps {} | ||
|
||
/** | ||
* SideBar is a sidebar component that can be used to display a list of items in a collapsible menu. | ||
* | ||
* @param {Object} props - The properties of the SideBar component. | ||
* @returns The rendered SideBar component. | ||
*/ | ||
const SideBar = forwardRef<HTMLDivElement, SideBarProps>((originProps, ref) => { | ||
const props = useDefaultProps(originProps, sideBarDefaultProps); | ||
const sideBarClass = usePrefixClass('side-bar'); | ||
|
||
const { onClick, onChange, children, defaultValue, value } = props; | ||
const [activeValue, onToggleActiveValue] = useDefault(value, defaultValue, onChange); | ||
|
||
const defaultIndex = useRef(-1); | ||
const updateChild = onToggleActiveValue; | ||
|
||
const [childrenList, setChildrenList] = useState([]); | ||
|
||
useEffect(() => { | ||
onChange?.(activeValue); | ||
}, [activeValue, onChange]); | ||
|
||
/** | ||
* Adds a child component to the children array. | ||
* | ||
* @param {React.Element} child - The child component to add. | ||
*/ | ||
const relation = useCallback((child) => { | ||
setChildrenList((prevChildrenList) => [...prevChildrenList, child]); | ||
}, []); | ||
|
||
/** | ||
* Removes a child component from the children array. | ||
* | ||
* @param {React.Element} child - The child component to remove. | ||
*/ | ||
const removeRelation = useCallback((child) => { | ||
setChildrenList((prevChildrenList) => prevChildrenList.filter((item) => item !== child)); | ||
}, []); | ||
|
||
/** | ||
* Handles the click event on a SideBar item. | ||
* | ||
* @param {string | number} cur - The value of the clicked item. | ||
* @param {string} label - The label of the clicked item. | ||
*/ | ||
const onClickItem = useCallback( | ||
(cur, label) => { | ||
onToggleActiveValue(cur); | ||
onClick?.(cur, label); | ||
}, | ||
[onToggleActiveValue, onClick], | ||
); | ||
|
||
const memoProviderValues = useMemo( | ||
() => ({ | ||
defaultIndex, | ||
activeValue, | ||
updateChild, | ||
childrenList, | ||
relation, | ||
removeRelation, | ||
onClickItem, | ||
}), | ||
[activeValue, updateChild, childrenList, relation, removeRelation, onClickItem], | ||
); | ||
|
||
return ( | ||
<div ref={ref} className={sideBarClass}> | ||
<SideBarProvider value={memoProviderValues}>{parseTNode(children)}</SideBarProvider> | ||
<div className={`${sideBarClass}__padding`}></div> | ||
</div> | ||
); | ||
}); | ||
|
||
export default memo(SideBar); |
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,22 @@ | ||
import React, { createContext, MutableRefObject, useMemo } from 'react'; | ||
import { ChangeHandler } from '../_util/useDefault'; | ||
import { TdSideBarProps } from './type'; | ||
|
||
/** | ||
* SideBarContext is a React context that stores the state and actions related to the SideBar component. | ||
*/ | ||
export const SideBarContext = createContext< | ||
{ | ||
defaultIndex: MutableRefObject<number>; | ||
activeValue: number | string | (number | string)[]; | ||
updateChild: ChangeHandler<number | string | (number | string)[], any[]>; | ||
relation: (child: any) => void; | ||
removeRelation: (child: any) => void; | ||
onClickItem: (cur: any, label: any) => void; | ||
} & Pick<TdSideBarProps, 'onChange' | 'onClick'> | ||
>(null); | ||
|
||
export function SideBarProvider({ children, value }) { | ||
const memoValue = useMemo(() => value, [value]); | ||
return <SideBarContext.Provider value={memoValue}>{children}</SideBarContext.Provider>; | ||
} |
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,58 @@ | ||
import React, { forwardRef, memo, useContext, useEffect } from 'react'; | ||
import cls from 'classnames'; | ||
import type { StyledProps } from '../common'; | ||
import type { TdSideBarItemProps } from './type'; | ||
import { SideBarContext } from './SideBarContext'; | ||
import Badge from '../badge'; | ||
import parseTNode from '../_util/parseTNode'; | ||
import { sideBarItemDefaultProps } from './defaultProps'; | ||
import useDefaultProps from '../hooks/useDefaultProps'; | ||
import { usePrefixClass } from '../hooks/useClass'; | ||
|
||
export interface SideBarItemProps extends TdSideBarItemProps, StyledProps {} | ||
|
||
const SideBarItem = forwardRef<HTMLDivElement, SideBarItemProps>((originProps, ref) => { | ||
const props = useDefaultProps(originProps, sideBarItemDefaultProps); | ||
const { badgeProps, disabled, icon, label, value } = props; | ||
const { relation, removeRelation, activeValue, onClickItem } = useContext(SideBarContext); | ||
|
||
const sideBarItemClass = usePrefixClass('side-bar-item'); | ||
|
||
useEffect(() => { | ||
relation(props); | ||
return () => { | ||
removeRelation(props); | ||
}; | ||
}, [props, relation, removeRelation]); | ||
|
||
const isActive = activeValue === value; | ||
|
||
const onClick = () => { | ||
if (!disabled) onClickItem(value, label); | ||
}; | ||
|
||
const isShowBadge = badgeProps?.count || badgeProps?.dot; | ||
|
||
return ( | ||
<div | ||
ref={ref} | ||
className={cls(sideBarItemClass, { | ||
[`${sideBarItemClass}--active`]: isActive, | ||
[`${sideBarItemClass}--disabled`]: disabled, | ||
})} | ||
onClick={onClick} | ||
> | ||
{isActive && ( | ||
<div> | ||
<div className={`${sideBarItemClass}__line`}></div> | ||
<div className={`${sideBarItemClass}__prefix`}></div> | ||
<div className={`${sideBarItemClass}__suffix`}></div> | ||
</div> | ||
)} | ||
{icon && <div className={`${sideBarItemClass}__icon`}>{parseTNode(icon)}</div>} | ||
{isShowBadge ? <Badge content={label} {...badgeProps} /> : <div>{label}</div>} | ||
</div> | ||
); | ||
}); | ||
|
||
export default memo(SideBarItem); |
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,3 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`SideBar 组件测试 > content 1`] = `null`; |
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,12 @@ | ||
import React from 'react'; | ||
import { describe, it, expect, render } from '@test/utils'; | ||
|
||
import SideBar from '../SideBar'; | ||
|
||
describe('SideBar 组件测试', () => { | ||
const SideBarText = 'SideBar组件'; | ||
it('content', async () => { | ||
const { queryByText } = render(<SideBar />); | ||
expect(queryByText(SideBarText)).toMatchSnapshot(); | ||
}); | ||
}); |
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,93 @@ | ||
import React, { useState, useEffect, useRef, useCallback } from 'react'; | ||
import { Grid, GridItem } from '../../grid'; | ||
import { SideBarProps, SideBarItemProps, SideBar, SideBarItem } from '..'; | ||
import './style/base.less'; | ||
|
||
const image = 'https://tdesign.gtimg.com/mobile/demos/example2.png'; | ||
const items = new Array(12).fill({ label: '标题文字', image }); | ||
|
||
const categories = [ | ||
{ label: '选项一', title: '标题一', badgeProps: {}, items }, | ||
{ label: '选项二', title: '标题二', badgeProps: { dot: true }, items: items.slice(0, 9) }, | ||
{ label: '选项三', title: '标题三', badgeProps: {}, items: items.slice(0, 9) }, | ||
{ label: '选项四', title: '标题四', badgeProps: { count: 6 }, items: items.slice(0, 6) }, | ||
{ label: '选项五', title: '标题五', badgeProps: {}, items: items.slice(0, 3) }, | ||
]; | ||
|
||
function SideBarWrapper() { | ||
const [sideBarIndex, setSideBarIndex] = useState<SideBarProps['value']>(1); | ||
const [offsetTopList, setOffsetTopList] = useState([]); | ||
const wrapperRef = useRef(null); | ||
|
||
const moveToActiveSideBar = useCallback( | ||
(index) => { | ||
if (wrapperRef.current) { | ||
wrapperRef.current.scrollTop = offsetTopList[index] - offsetTopList[0]; | ||
} | ||
}, | ||
[offsetTopList], | ||
); | ||
|
||
const onSideBarChange = (value) => { | ||
setSideBarIndex(value); | ||
moveToActiveSideBar(value); | ||
}; | ||
|
||
const onScroll = (e) => { | ||
const threshold = offsetTopList[0]; | ||
const { scrollTop } = e.target; | ||
if (scrollTop < threshold) { | ||
setSideBarIndex(0); | ||
return; | ||
} | ||
const index = offsetTopList.findIndex((top) => top > scrollTop && top - scrollTop <= threshold); | ||
if (index > -1) { | ||
setSideBarIndex(index); | ||
} | ||
}; | ||
|
||
const onSideBarClick = (value: SideBarProps['value'], label: SideBarItemProps['label']) => { | ||
console.log('=onSideBarClick===', value, label); | ||
}; | ||
|
||
useEffect(() => { | ||
const newOffsetTopList = []; | ||
const titles = wrapperRef.current.querySelectorAll('.title'); | ||
titles.forEach((title) => { | ||
newOffsetTopList.push(title.offsetTop); | ||
}); | ||
setOffsetTopList(newOffsetTopList); | ||
moveToActiveSideBar(sideBarIndex); | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [sideBarIndex]); | ||
|
||
return ( | ||
<> | ||
<div className="side-bar-wrapper section-base"> | ||
<SideBar value={sideBarIndex} onChange={onSideBarChange} onClick={onSideBarClick}> | ||
{categories.map((item, index) => ( | ||
<SideBarItem key={index} value={index} label={item.label} badgeProps={item.badgeProps} /> | ||
))} | ||
</SideBar> | ||
<div ref={wrapperRef} className="content" onScroll={onScroll}> | ||
{categories.map((item, index) => ( | ||
<div key={index} className="section"> | ||
<div className="title">{item.title || item.label}</div> | ||
<Grid column={3} border={false}> | ||
{item.items.map((cargo, cargoIndex) => ( | ||
<GridItem | ||
key={cargoIndex} | ||
text={cargo.label} | ||
image={{ src: cargo.image, shape: 'round', lazy: true }} | ||
></GridItem> | ||
))} | ||
</Grid> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default SideBarWrapper; |
Oops, something went wrong.