Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/hooks/useElementHeight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useRef, useState, useEffect, useCallback } from 'react';

export interface UseElementHeightOptions {
/** 是否立即计算高度 */
immediate?: boolean;
/** 监听窗口大小变化 */
resizeObserver?: boolean;
}

/**
* 用于计算元素高度的 hook
* @param targetRef 目标元素的 ref
* @param options 配置选项
* @returns 返回高度和重新计算的方法
*/
export default function useElementHeight(
targetRef: React.RefObject<HTMLElement | null>,
options: UseElementHeightOptions = {},
) {
const { immediate = true, resizeObserver = false } = options;
const [elementHeight, setElementHeight] = useState(0);
const resizeObserverInstance = useRef<ResizeObserver | null>(null);

const calculateHeight = useCallback(() => {
const currentElement = targetRef.current;
if (currentElement) {
// 使用 getBoundingClientRect 获取精确高度
const { height } = currentElement.getBoundingClientRect();
setElementHeight(height);
}
}, [targetRef]);

const setupResizeObserver = useCallback(() => {
if (!resizeObserver) return;

const currentElement = targetRef.current;
if (currentElement && window.ResizeObserver) {
resizeObserverInstance.current = new ResizeObserver(() => {
calculateHeight();
});
resizeObserverInstance.current.observe(currentElement);
}
}, [resizeObserver, calculateHeight, targetRef]);

const cleanupResizeObserver = useCallback(() => {
if (resizeObserverInstance.current) {
resizeObserverInstance.current.disconnect();
resizeObserverInstance.current = null;
}
}, []);

useEffect(() => {
if (immediate) {
// 使用 setTimeout 确保在 DOM 更新后计算高度
const timeoutId = setTimeout(calculateHeight, 0);
return () => clearTimeout(timeoutId);
}
}, [immediate, calculateHeight]);

useEffect(() => {
setupResizeObserver();
return cleanupResizeObserver;
}, [setupResizeObserver, cleanupResizeObserver]);

return {
height: elementHeight,
calculateHeight,
};
}
98 changes: 61 additions & 37 deletions src/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import type { CSSProperties } from 'react';
import { ChevronLeftIcon } from 'tdesign-icons-react';
import ClassNames from 'classnames';
import classNames from 'classnames';
import { usePrefixClass } from '../hooks/useClass';
import { StyledProps } from '../common';
import { TdNavbarProps } from './type';
Expand All @@ -26,23 +26,26 @@ const Navbar: React.FC<NavbarProps> = (originProps) => {
right,
fixed,
animation,
placeholder,
zIndex,
className,
safeAreaInsetTop,
style,
onLeftClick,
onRightClick,
} = props;
const prefix = usePrefixClass('navbar');
const prefixClass = usePrefixClass();
const navbarClass = usePrefixClass('navbar');
const animationSuffix = useMemo(() => (animation ? '-animation' : ''), [animation]);

const cls = useCallback((name?: string) => (name ? `${prefix}__${name}` : prefix), [prefix]);

// 左侧胶囊区域
const leftCapsuleContent = useMemo(() => {
if (!capsule) {
const renderCapsule = () => {
const capsuleContent = parseTNode(capsule);
if (!capsuleContent) {
return null;
}
return <div className={cls('capsule')}>{capsule}</div>;
}, [capsule, cls]);
return <div className={`${navbarClass}__capsule`}>{capsuleContent}</div>;
};

const titleChildren = useMemo(() => {
let titleNode = children || title;
Expand All @@ -56,49 +59,70 @@ const Navbar: React.FC<NavbarProps> = (originProps) => {
}
}

return isStringTitle ? <span className={cls('center-title')}>{parseTNode(titleNode)}</span> : parseTNode(titleNode);
}, [children, cls, title, titleMaxLength]);
return isStringTitle ? (
<span className={`${navbarClass}__center-title`}>{parseTNode(titleNode)}</span>
) : (
parseTNode(titleNode)
);
}, [children, navbarClass, title, titleMaxLength]);

// 右侧icon
const rightContent = useMemo(
() =>
right ? (
<div className={cls('right')} onClick={onRightClick}>
{parseTNode(right)}
</div>
) : null,
[cls, right, onRightClick],
);
const renderRight = () =>
right ? (
<div className={`${navbarClass}__right`} onClick={onRightClick}>
{parseTNode(right)}
</div>
) : null;

const navClass = useMemo<string>(
() =>
ClassNames(
prefix,
{ [`${prefix}--fixed`]: fixed },
visible ? `${prefix}--visible${animationSuffix}` : `${prefix}--hide${animationSuffix}`,
classNames(
navbarClass,
{ [`${navbarClass}--fixed`]: fixed, [`${prefixClass}-safe-area-top`]: safeAreaInsetTop },
visible ? `${navbarClass}--visible${animationSuffix}` : `${navbarClass}--hide${animationSuffix}`,
),
[prefix, fixed, visible, animationSuffix],
[navbarClass, prefixClass, fixed, visible, animationSuffix, safeAreaInsetTop],
);

const navStyle = useMemo<CSSProperties>(
() => ({
position: fixed ? 'fixed' : 'relative',
zIndex,
...style,
}),
[fixed, style],
[zIndex, style],
);

const renderLeftArrow = () => {
if (leftArrow) {
return <ChevronLeftIcon className={`${navbarClass}__left-arrow`} />;
}
return null;
};

const renderLeft = () => (
<div className={`${navbarClass}__left`} onClick={onLeftClick}>
{renderLeftArrow()}
{parseTNode(left)}
{renderCapsule()}
</div>
);

const renderCenter = () => <div className={`${navbarClass}__center`}>{titleChildren}</div>;

const renderPlaceholder = () => {
if (fixed && placeholder) {
return <div className={`${navbarClass}__placeholder`}></div>;
}
return null;
};

return (
<div className={ClassNames(navClass, className)} style={navStyle}>
{fixed && <div className={cls('placeholder')}></div>}
<div className={cls(`content`)}>
<div className={cls(`left`)} onClick={onLeftClick}>
{leftArrow && <ChevronLeftIcon className={cls('left-arrow')} />}
{parseTNode(left)}
{leftCapsuleContent}
</div>
<div className={cls(`center`)}>{titleChildren}</div>
{rightContent}
<div className={classNames(navClass, className)} style={navStyle}>
{renderPlaceholder()}
<div className={`${navbarClass}__content`}>
{renderLeft()}
{renderCenter()}
{renderRight()}
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/navbar/_example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import './style/index.less';
export default function Base() {
return (
<div className="tdesign-mobile-demo">
<Navbar title="Navbar 导航条" style={{ zIndex: 999 }} leftArrow />
<Navbar title="Navbar 导航条" style={{ zIndex: 999 }} leftArrow fixed />

<TDemoHeader title="Navbar 导航栏" summary="用于不同页面之间切换或者跳转,位于内容区的上方,系统状态栏的下方。" />
<TDemoBlock title="01 组件类型" summary="基础导航栏">
Expand Down
10 changes: 9 additions & 1 deletion src/navbar/defaultProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@

import { TdNavbarProps } from './type';

export const navbarDefaultProps: TdNavbarProps = { animation: true, fixed: true, leftArrow: false, visible: true };
export const navbarDefaultProps: TdNavbarProps = {
animation: true,
fixed: true,
leftArrow: false,
placeholder: false,
safeAreaInsetTop: true,
visible: true,
zIndex: 1,
};
18 changes: 10 additions & 8 deletions src/navbar/navbar.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@

## API


### Navbar Props

name | type | default | description | required
-- | -- | -- | -- | --
className | String | - | className of component | N
style | Object | - | CSS(Cascading Style Sheets),Typescript`React.CSSProperties` | N
style | Object | - | CSS(Cascading Style Sheets),Typescript: `React.CSSProperties` | N
animation | Boolean | true | \- | N
background | String | - | `deprecated`。background | N
capsule | TElement | - | Typescript`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
capsule | TElement | - | Typescript: `TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
fixed | Boolean | true | \- | N
left | TNode | - | Typescript`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
left | TNode | - | Typescript: `string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
leftArrow | Boolean | false | \- | N
right | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
title | TNode | - | page title。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
placeholder | Boolean | false | `0.21.1` | N
right | TNode | - | Typescript: `string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
safeAreaInsetTop | Boolean | true | \- | N
title | TNode | - | page title。Typescript: `string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
titleMaxLength | Number | - | \- | N
visible | Boolean | true | \- | N
onLeftClick | Function | | Typescript:`() => void`<br/> | N
onRightClick | Function | | Typescript:`() => void`<br/> | N
zIndex | Number | 1 | \- | N
onLeftClick | Function | | Typescript: `() => void`<br/> | N
onRightClick | Function | | Typescript: `() => void`<br/> | N

### CSS Variables

Expand Down
4 changes: 4 additions & 0 deletions src/navbar/navbar.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
:: BASE_DOC ::

## API

### Navbar Props

名称 | 类型 | 默认值 | 描述 | 必传
Expand All @@ -13,10 +14,13 @@ capsule | TElement | - | 左侧胶囊区域。TS 类型:`TNode`。[通用类
fixed | Boolean | true | 是否固定在顶部 | N
left | TNode | - | 左侧区域。值为 `string` 表示文本,为其他表示自定义内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
leftArrow | Boolean | false | 是否展示左侧箭头 | N
placeholder | Boolean | false | `0.21.1`。固定在顶部时是否开启占位 | N
right | TNode | - | 右侧区域。值为 `string` 表示文本,为其他表示自定义内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
safeAreaInsetTop | Boolean | true | 是否开启顶部安全区适配 | N
title | TNode | - | 页面标题。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N
titleMaxLength | Number | - | 标题文字最大长度,超出的范围使用 `...` 表示 | N
visible | Boolean | true | 是否显示 | N
zIndex | Number | 1 | 导航条层级 | N
onLeftClick | Function | | TS 类型:`() => void`<br/>点击左侧区域时触发 | N
onRightClick | Function | | TS 类型:`() => void`<br/>点击右侧区域时触发 | N

Expand Down
15 changes: 15 additions & 0 deletions src/navbar/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@ export interface TdNavbarProps {
* @default false
*/
leftArrow?: boolean;
/**
* 固定在顶部时是否开启占位
* @default false
*/
placeholder?: boolean;
/**
* 右侧区域。值为 `string` 表示文本,为其他表示自定义内容
*/
right?: TNode;
/**
* 是否开启顶部安全区适配
* @default true
*/
safeAreaInsetTop?: boolean;
/**
* 页面标题
*/
Expand All @@ -47,6 +57,11 @@ export interface TdNavbarProps {
* @default true
*/
visible?: boolean;
/**
* 导航条层级
* @default 1
*/
zIndex?: number;
/**
* 点击左侧区域时触发
*/
Expand Down
2 changes: 2 additions & 0 deletions src/style/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import '../_common/style/mobile/_global.less';

import '../_common/style/mobile/theme/_index.less';
Loading
Loading