diff --git a/site/sidebar.config.ts b/site/sidebar.config.ts index 9efd0cf..eb7d716 100644 --- a/site/sidebar.config.ts +++ b/site/sidebar.config.ts @@ -115,7 +115,7 @@ export default [ title: 'Breadcrumb 面包屑', name: 'breadcrumb', path: '/components/breadcrumb', - // component: () => import('tdesign-web-components/breadcrumb/README.md'), + component: () => import('tdesign-web-components/breadcrumb/README.md'), }, { title: 'BackTop 回到顶部', diff --git a/src/_common b/src/_common index 11af9d6..fb55c35 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 11af9d6eed1f28a10dff5c3d1fd7cdb9a5031c9a +Subproject commit fb55c3516c7357878f234dc09aa92efdf4cf2b69 diff --git a/src/_util/dom.ts b/src/_util/dom.ts index 462fb03..eaca149 100644 --- a/src/_util/dom.ts +++ b/src/_util/dom.ts @@ -194,3 +194,9 @@ export function setStyle(style: CSSStyleDeclaration, key: string, value: string (style as any)[key] = `${value}px`; } } + +// 用于判断节点内容是否溢出 +export const isNodeOverflow = (ele: Element | Element[]): boolean => { + const { clientWidth = 0, scrollWidth = 0 } = ele as Element; + return scrollWidth > clientWidth; +}; diff --git a/src/breadcrumb/README.md b/src/breadcrumb/README.md new file mode 100644 index 0000000..3a00146 --- /dev/null +++ b/src/breadcrumb/README.md @@ -0,0 +1,49 @@ +--- +title: Breadcrumb 面包屑 +description: 显示当前页面在系统层级结构的位置,并能返回之前任意层级的页面。 +isComponent: true +usage: { title: '', description: '' } +spline: base +--- + +### 基础面包屑 +适用于广泛的基础用法,系统拥有超过两级以上的层级结构,用于切换向上任意层级的内容。 +{{ base }} + +### 自定义分隔符的面包屑 +通过 separator 属性自定义分隔符,建议用图标而非文本符号。 +{{ custom }} + +### 使用 options 配置面包屑 +使用 `options` 属性配置面包屑内容。 +{{ options }} + +### 带跳转/点击的面包屑 +自定义响应点击事件。 + +{{ href }} +## API + +### Breadcrumb Props + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +maxItemWidth | String | undefined | 单项最大宽度,超出后会以省略号形式呈现 | N +options | Array | - | 面包屑项,功能同 BreadcrumbItem。TS 类型:`Array` | N +separator | TNode | - | 自定义分隔符。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N + + +### BreadcrumbItem Props + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +content | TNode | - | 子元素。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +disabled | Boolean | - | 是否禁用当前项点击 | N +href | String | - | 跳转链接 | N +maxWidth | String | undefined | 最大宽度,超出后会以省略号形式呈现。优先级高于 Breadcrumb 中的 maxItemWidth | N +target | String | _self | 链接或路由跳转方式。可选项:_blank/_self/_parent/_top | N +onClick | Function | | TS 类型:`(e: MouseEvent) => void`
点击时触发 | N diff --git a/src/breadcrumb/_example/base.tsx b/src/breadcrumb/_example/base.tsx new file mode 100644 index 0000000..e02f6c7 --- /dev/null +++ b/src/breadcrumb/_example/base.tsx @@ -0,0 +1,15 @@ +import 'tdesign-web-components/breadcrumb'; + +import { Component } from 'omi'; + +export default class Breadcrumb extends Component { + render() { + return ( + + 页面一 + 页面2面包屑文案超长时悬浮显示文案全部信息 + 面包屑中文案过长时可缩略显示,鼠标hover时显示全部 + + ); + } +} diff --git a/src/breadcrumb/_example/custom.tsx b/src/breadcrumb/_example/custom.tsx new file mode 100644 index 0000000..c3c0384 --- /dev/null +++ b/src/breadcrumb/_example/custom.tsx @@ -0,0 +1,19 @@ +import 'tdesign-web-components/breadcrumb'; + +export default function Breadcrumb() { + return ( + <> + + 页面1 + 页面2 + 页面3 + {'>'} + + + 页面1 + 页面2 + 页面3 + + + ); +} diff --git a/src/breadcrumb/_example/href.tsx b/src/breadcrumb/_example/href.tsx new file mode 100644 index 0000000..9a90f12 --- /dev/null +++ b/src/breadcrumb/_example/href.tsx @@ -0,0 +1,31 @@ +import 'tdesign-web-components/breadcrumb'; +import 'tdesign-web-components/space'; + +import { Component, signal } from 'omi'; + +export default class Breadcrumb extends Component { + count = signal(0); + + // 点击事件 + onClickItem = () => { + this.count.value += 1; + }; + + render() { + return ( + + + + 页面1 + + 页面2 + + 页面3 + + 自定义点击 + +
点击次数: {this.count.value}
+
+ ); + } +} diff --git a/src/breadcrumb/_example/options.tsx b/src/breadcrumb/_example/options.tsx new file mode 100644 index 0000000..530dbc0 --- /dev/null +++ b/src/breadcrumb/_example/options.tsx @@ -0,0 +1,6 @@ +import 'tdesign-web-components/breadcrumb'; + +export default function Breadcrumb() { + const options = [{ content: '页面1' }, { content: '页面2' }, { content: '页面3', href: 'https://github.com/' }]; + return ; +} diff --git a/src/breadcrumb/breadcrumb-item.tsx b/src/breadcrumb/breadcrumb-item.tsx new file mode 100644 index 0000000..d6c8cfb --- /dev/null +++ b/src/breadcrumb/breadcrumb-item.tsx @@ -0,0 +1,123 @@ +import 'tdesign-icons-web-components/esm/components/chevron-right'; +import '../tooltip'; + +import { bind, Component, OmiDOMAttributes, tag } from 'omi'; + +import classname, { getClassPrefix } from '../_util/classname'; +import { isNodeOverflow } from '../_util/dom'; +import { TNode } from '../common'; +import { TdBreadcrumbItemProps } from './type'; + +interface BreadcrumbItemProps extends TdBreadcrumbItemProps, OmiDOMAttributes {} + +interface LocalTBreadcrumb { + separator: TNode | string; + maxItemWidth: string; +} + +const localTBreadcrumbOrigin: LocalTBreadcrumb = { + separator: '', + maxItemWidth: undefined, +}; + +@tag('t-breadcrumb-item') +export default class BreadcrumbItem extends Component { + static css = []; + + static isLightDOM = true; + + className = `${getClassPrefix()}-breadcrumb__item`; + + static defaultProps = { + href: '', + target: '_self', + isLast: false, + }; + + static propsType = { + content: [String, Number, Object, Function], + disabled: Boolean, + href: String, + maxWidth: String, + target: String, + onClick: Function, + }; + + inject = ['tBreadcrumb']; + + localTBreadcrumb = localTBreadcrumbOrigin; + + isCutOff = false; + + install() { + this.injection.tBreadcrumb && (this.localTBreadcrumb = this.injection.tBreadcrumb); + } + + adjustCutOff() { + setTimeout(() => { + const breadcrumbText = this.querySelector(`.${getClassPrefix()}-breadcrumb__inner-text`); + + if (breadcrumbText?.clientWidth) { + const isCutOff = isNodeOverflow(breadcrumbText); + if (this.isCutOff !== isCutOff) { + this.isCutOff = isCutOff; + this.update(); + } + } + }); + } + + ready(): void { + this.adjustCutOff(); + } + + updated(): void { + this.adjustCutOff(); + } + + @bind + handleClick(event: MouseEvent): void { + event.stopImmediatePropagation(); + if (!this.props.disabled) { + this.props.onClick?.(event); + } + } + + render() { + const { children, className, disabled, href, target, content, maxWidth } = this.props; + const { separator, maxItemWidth } = this.localTBreadcrumb; + const classPrefix = getClassPrefix(); + const textClass = [`${classPrefix}-breadcrumb--text-overflow`]; + if (disabled) { + textClass.push(`${classPrefix}-is-disabled`); + } + const separatorContent = separator || ; + const innerMaxWidth = maxWidth || maxItemWidth || '120'; + const maxWithStyle = { 'max-width': `${innerMaxWidth}px` }; + + const textContent = ( + + {children || content} + + ); + + let itemContent; + if (href && !disabled) { + textClass.push(`${classPrefix}-link`); + itemContent = ( + + {textContent} + + ); + } else { + itemContent = {textContent}; + } + + return ( +
+ {this.isCutOff ? {itemContent} : itemContent} + {separatorContent} +
+ ); + } +} diff --git a/src/breadcrumb/breadcrumb.tsx b/src/breadcrumb/breadcrumb.tsx new file mode 100644 index 0000000..d13b003 --- /dev/null +++ b/src/breadcrumb/breadcrumb.tsx @@ -0,0 +1,52 @@ +import './breadcrumb-item'; + +import { Component, OmiDOMAttributes, tag } from 'omi'; + +import classname, { getClassPrefix } from '../_util/classname'; +import { getChildrenArray } from '../_util/component'; +import { TdBreadcrumbProps } from './type'; + +interface BreadcrumbProps extends TdBreadcrumbProps, OmiDOMAttributes {} + +@tag('t-breadcrumb') +export default class Breadcrumb extends Component { + className = `${getClassPrefix()}-breadcrumb`; + + static propTypes = { + maxItemWidth: String, + options: Object, + separator: [String, Number, Object, Function], + }; + + get contentNodes() { + const { children, options } = this.props; + const childrenArray = getChildrenArray(children); + let content = childrenArray + .filter((child) => child.attributes?.slot !== 'separator' && child.nodeName === 't-breadcrumb-item') + .map((item) => item); + + if (options && options.length) { + content = options.map((option) => ); + } + return content; + } + + provide = { + tBreadcrumb: { + separator: '', + maxItemWidth: '', + }, + }; + + beforeRender(): void { + const { separator, maxItemWidth, children } = this.props; + const separatorSlot = getChildrenArray(children).find((child) => child.attributes?.slot === 'separator'); + this.provide.tBreadcrumb.separator = separator || separatorSlot; + this.provide.tBreadcrumb.maxItemWidth = maxItemWidth; + } + + render() { + const { className } = this.props; + return
{this.contentNodes}
; + } +} diff --git a/src/breadcrumb/index.ts b/src/breadcrumb/index.ts new file mode 100644 index 0000000..b25451d --- /dev/null +++ b/src/breadcrumb/index.ts @@ -0,0 +1,11 @@ +import './style/index.js'; + +import _Breadcrumb from './breadcrumb'; +import _BreadcrumbItem from './breadcrumb-item'; + +export * from './type'; + +export type { TdBreadcrumbProps, TdBreadcrumbItemProps } from './type.ts'; +export const Breadcrumb = _Breadcrumb; +export const BreadcrumbItem = _BreadcrumbItem; +export default Breadcrumb; diff --git a/src/breadcrumb/style/index.js b/src/breadcrumb/style/index.js new file mode 100644 index 0000000..c4408cc --- /dev/null +++ b/src/breadcrumb/style/index.js @@ -0,0 +1,10 @@ +import { css, globalCSS } from 'omi'; + +// 为了做主题切换 +import styles from '../../_common/style/web/components/breadcrumb/_index.less'; + +export const styleSheet = css` + ${styles} +`; + +globalCSS(styleSheet); diff --git a/src/breadcrumb/type.ts b/src/breadcrumb/type.ts new file mode 100644 index 0000000..e36edff --- /dev/null +++ b/src/breadcrumb/type.ts @@ -0,0 +1,53 @@ +import { TNode } from '../common'; + +export interface TdBreadcrumbProps { + /** + * 类名 + */ + className?: string; + /** + * 单项最大宽度,超出后会以省略号形式呈现 + */ + maxItemWidth?: string; + /** + * 面包屑项,功能同 BreadcrumbItem + */ + options?: Array; + /** + * 自定义分隔符 + */ + separator?: string | TNode; +} + +export interface TdBreadcrumbItemProps { + /** + * 类名 + */ + className?: string; + /** + * + */ + content?: TNode; + /** + * 是否禁用当前项点击 + */ + disabled?: boolean; + /** + * 跳转链接 + * @default '' + */ + href?: string; + /** + * 最大宽度,超出后会以省略号形式呈现。优先级高于 Breadcrumb 中的 maxItemWidth + */ + maxWidth?: string; + /** + * 链接或路由跳转方式 + * @default _self + */ + target?: '_blank' | '_self' | '_parent' | '_top'; + /** + * 点击时触发 + */ + onClick?: (e: MouseEvent) => void; +} diff --git a/src/index.ts b/src/index.ts index fefa281..dfdf923 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export * from './alert'; export * from './avatar'; export * from './back-top'; export * from './badge'; +export * from './breadcrumb'; export * from './button'; export * from './card'; export * from './checkbox';