diff --git a/DEVELOP_GUIDE.md b/DEVELOP_GUIDE.md index 14ee0c6..80ca922 100644 --- a/DEVELOP_GUIDE.md +++ b/DEVELOP_GUIDE.md @@ -105,7 +105,7 @@ npm run start ### 组件页路由配置 -每一个组件页,都是一个 md 文件,参考 `/site/sidebar.config.js` 已有定义,直接按照模板添加即可 +每一个组件页,都是一个 md 文件,参考 `/site/sidebar.config.ts` 已有定义,直接按照模板添加即可 ```javascript { diff --git a/site/sidebar.config.ts b/site/sidebar.config.ts index 25c1b67..18368d6 100644 --- a/site/sidebar.config.ts +++ b/site/sidebar.config.ts @@ -130,6 +130,12 @@ export default [ path: '/components/calendar', // component: () => import('tdesign-web-components/calendar/README.md'), }, + { + title: 'Collapse 折叠面板', + name: 'Collapse', + path: '/components/collapse', + component: () => import('tdesign-web-components/collapse/README.md'), + }, { title: 'Table 表格', name: 'table', diff --git a/src/collapse/README.md b/src/collapse/README.md new file mode 100644 index 0000000..5150e5a --- /dev/null +++ b/src/collapse/README.md @@ -0,0 +1,60 @@ +--- +title: Collapse 折叠面板 +description: 可以将较多或较复杂的内容进行分组,分组内容区可以折叠展开或隐藏。 +isComponent: true +usage: { title: '', description: '' } +spline: data +--- + +### 基础折叠面板 + +基础折叠面板,可自定义面板内容。 + +{{ base }} + +### 手风琴模式折叠面板 + +手风琴模式折叠面板,一次只能打开一个面板。 + +{{ mutex }} + +### 可设置图标的折叠面板 + +可设置是否显示展开图标以及图标展示的位置 + +{{ icon }} + +### 可设置右侧操作的折叠面板 + +可自定义面板右侧操作区域 + +{{ rightSlot }} + +### 不同模式的折叠面板 + + +{{ other }} + +## API + +### Collapse Props + +| 名称 | 类型 | 默认值 | 说明 | 必传 | +| ------------------- | -------- | ------ | ----------------------------------------------------------------------------------------- | ---- | +| borderless | Boolean | false | 是否为无边框模式 | N | +| defaultExpandAll | Boolean | false | 默认是否展开全部 | N | +| disabled | Boolean | - | 是否禁用面板展开/收起操作 | N | +| expandIconPlacement | String | left | 展开图标的位置,左侧或右侧。可选项:left/right | N | +| expandMutex | Boolean | false | 每个面板互斥展开,每次只展开一个面板 | N | +| expandOnRowClick | Boolean | true | 是否允许点击整行标题展开面板 | N | +| value | Array | [] | 展开的面板集合。TS 类型:`CollapseValue` `type CollapseValue = Array`。 | N | +| onChange | Function | | TS 类型:`(value: CollapseValue) => void`
切换面板时触发,返回变化的值 | N | + + +### CollapsePanel Props + +| 名称 | 类型 | 默认值 | 说明 | 必传 | +| ----------------- | --------------- | --------- | ---------------------------------------------------------------- | ---- | +| destroyOnCollapse | Boolean | false | 当前面板处理折叠状态时,是否销毁面板内容 | N | +| disabled | Boolean | undefined | 禁止当前面板展开,优先级大于 Collapse 的同名属性 | N | +| value | String / Number | - | 必需。当前面板唯一标识,如果值为空则取当前面下标兜底作为唯一标识 | Y | diff --git a/src/collapse/_example/base.tsx b/src/collapse/_example/base.tsx new file mode 100644 index 0000000..9d77317 --- /dev/null +++ b/src/collapse/_example/base.tsx @@ -0,0 +1,27 @@ +import 'tdesign-web-components/collapse'; + +import { bind, Component, signal } from 'omi'; + +export default class Demo extends Component { + checked = signal(true); + + @bind + onChange(value) { + console.log('onChange.value', value); + } + + render() { + return ( + + +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+
+ ); + } +} diff --git a/src/collapse/_example/icon.tsx b/src/collapse/_example/icon.tsx new file mode 100644 index 0000000..7957928 --- /dev/null +++ b/src/collapse/_example/icon.tsx @@ -0,0 +1,37 @@ +import 'tdesign-web-components/collapse'; +import 'tdesign-web-components/space'; +import 'tdesign-web-components/icon'; + +import { bind, Component, signal } from 'omi'; + +export default class Demo extends Component { + checked = signal(true); + + @bind + onChange(value) { + console.log('onChange.value', value); + } + + render() { + return ( + + + +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
自定义图标
+ + 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
自定义图标
+ + 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+
+
+ ); + } +} diff --git a/src/collapse/_example/mutex.tsx b/src/collapse/_example/mutex.tsx new file mode 100644 index 0000000..dbb6aba --- /dev/null +++ b/src/collapse/_example/mutex.tsx @@ -0,0 +1,40 @@ +import 'tdesign-web-components/collapse'; +import 'tdesign-web-components/space'; + +import { bind, Component, signal } from 'omi'; + +export default class Demo extends Component { + currentItem = signal('[]'); + + @bind + onChange(value) { + this.currentItem.value = value.valueOf(); + console.log('currentItem', this.currentItem); + } + + render() { + return ( + + + +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+
+
当前展开项:{this.currentItem.value}
+
+ ); + } +} diff --git a/src/collapse/_example/other.tsx b/src/collapse/_example/other.tsx new file mode 100644 index 0000000..f384d38 --- /dev/null +++ b/src/collapse/_example/other.tsx @@ -0,0 +1,54 @@ +import 'tdesign-web-components/switch'; +import 'tdesign-web-components/collapse'; +import 'tdesign-web-components/space'; + +import { Component, signal } from 'omi'; + +export default class Demo extends Component { + static css = ` +.button-area { + margin-left: 20px; + margin-top: 10px; +} +.button-text { + display: inline-block; + width: 100px; + font-size: 13px; + margin-bottom: 2px; +} +`; + + disabled = signal(false); + + expandIcon = signal(true); + + borderless = signal(false); + + render() { + const { borderless, expandIcon, disabled } = this; + return ( + + + +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
这是一个折叠标题
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+
+
+
+ 全部禁用 + (this.disabled.value = !this.disabled.value)} /> +
+
+ 无边框 + (this.borderless.value = !this.borderless.value)} /> +
+
+
+ ); + } +} diff --git a/src/collapse/_example/rightSlot.tsx b/src/collapse/_example/rightSlot.tsx new file mode 100644 index 0000000..c530ed7 --- /dev/null +++ b/src/collapse/_example/rightSlot.tsx @@ -0,0 +1,38 @@ +import 'tdesign-web-components/collapse'; +import 'tdesign-web-components/button'; + +import { bind, Component, signal } from 'omi'; + +export default class Demo extends Component { + checked = signal(true); + + @bind + onChange(value) { + console.log('onChange.value', value); + } + + render() { + return ( + + +
这是一个折叠标题
+
+ + 操作 + +
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+ +
禁用状态
+
+ + 操作 + +
+ 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 +
+
+ ); + } +} diff --git a/src/collapse/collapse-panel.tsx b/src/collapse/collapse-panel.tsx new file mode 100644 index 0000000..8654565 --- /dev/null +++ b/src/collapse/collapse-panel.tsx @@ -0,0 +1,156 @@ +import '../common/fake-arrow'; + +import { bind, Component, computed, signal, tag } from 'omi'; + +import classname, { classPrefix } from '../_util/classname'; +import { StyledProps, TNode } from '../common'; +import type { TdCollapsePanelProps } from './type'; + +export interface CollapsePanelProps extends TdCollapsePanelProps, StyledProps {} + +@tag('t-collapse-panel') +export default class CollapsePanel extends Component { + static isLightDom = true; + + innerValue = signal(0); + + className = `${classPrefix}-collapse-panel`; + + isDisabled = signal(false); + + install(): void { + const { getUniqId, updateCollapseValue, defaultExpandAll } = this.injection; + + const { value } = this.props; + + this.innerValue = computed(() => (value === undefined ? getUniqId() : value)); + + if (defaultExpandAll.value) { + updateCollapseValue(this.innerValue.value); + } + + this.isDisabled = computed(() => this.props.disabled || this.injection.disabled.value); + } + + inject = [ + 'getUniqId', + 'collapseValue', + 'updateCollapseValue', + 'borderless', + 'defaultExpandAll', + 'disabled', + 'collapseProps', + 'expandIconPlacement', + 'expandOnRowClick', + ]; + + @bind + getChild(name) { + const children = this.props.children || []; + + if (!Array.isArray(children)) { + return; + } + const child = children.find((item) => item?.attributes?.slot === name); + return child; + } + + @bind + getDefaultSlot() { + const children = this.props.children || []; + if (!Array.isArray(children)) { + return children; + } + return children.find((item) => !item?.attributes?.slot); + } + + @bind + handleClick(event, fromHeader = false) { + if (this.isDisabled.value) { + return; + } + if (fromHeader && !this.injection.expandOnRowClick.value) { + return; + } + this.injection.updateCollapseValue(this.innerValue.value); + event.stopPropagation(); + } + + renderIcon() { + const { expandIconPlacement, collapseValue } = this.injection; + const isActive = collapseValue.value.includes(this.innerValue.value); + + return ( +
+ {this.getChild('expandIcon') || ( + + )} +
+ ); + } + + renderHeader() { + const { expandIconPlacement, expandOnRowClick } = this.injection; + return ( +
this.handleClick(e, true)} + > +
+ {expandIconPlacement.value === 'left' && this.renderIcon()} +
+ +
{this.getChild('header')}
+
+
+
e.stopPropagation()}> + {this.getChild('headerRightContent')} +
+ {expandIconPlacement.value === 'right' && this.renderIcon()} +
+
+ ); + } + + renderBody() { + const isActive = this.injection.collapseValue.value.includes(this.innerValue.value); + + const { destroyOnCollapse, children } = this.props; + + return destroyOnCollapse && !isActive ? null : ( +
+
+ {this.getDefaultSlot()} + {children} +
+
+ ); + } + + render(props: TdCollapsePanelProps): TNode { + const { className } = props; + + return ( +
+
+ {this.renderHeader()} + {this.renderBody()} +
+
+ ); + } +} diff --git a/src/collapse/collapse.tsx b/src/collapse/collapse.tsx new file mode 100644 index 0000000..8acfa01 --- /dev/null +++ b/src/collapse/collapse.tsx @@ -0,0 +1,91 @@ +import { bind, Component, signal, tag } from 'omi'; + +import classname, { classPrefix } from '../_util/classname'; +import { StyledProps, TNode } from '../common'; +import { TdCollapseProps } from './type'; + +export interface CollapseProps extends TdCollapseProps, StyledProps {} + +@tag('t-collapse') +export default class Collapse extends Component { + collapseValue = signal([]); + + innerBorderless = signal(false); + + innerDefaultExpandAll = signal(false); + + innerDisabled = signal(false); + + innerExpandIconPlacement: Omi.SignalValue = signal('left'); + + innerExpandOnRowClick = signal(true); + + @bind + updateCollapseValue(value) { + const index = this.collapseValue.value.indexOf(value); + let newValue = [...this.collapseValue.value]; + if (index >= 0) { + newValue.splice(index, 1); + } else if (this.props.expandMutex) { + newValue = [value]; + } else { + newValue.push(value); + } + this.collapseValue.value = newValue; + this.props?.onChange?.(newValue); + } + + provide = { + getUniqId: (() => { + let index = 0; + return () => (index += 1); + })(), + collapseValue: this.collapseValue, + updateCollapseValue: this.updateCollapseValue, + borderless: this.innerBorderless, + defaultExpandAll: this.innerDefaultExpandAll, + collapseProps: this.props, + disabled: this.innerDisabled, + expandIconPlacement: this.innerExpandIconPlacement, + expandOnRowClick: this.innerExpandOnRowClick, + }; + + innerChecked: any = signal(null); + + install(): void { + const { value, borderless, defaultExpandAll, disabled, expandIconPlacement, expandOnRowClick } = this.props; + this.collapseValue.value = value || []; + this.innerBorderless.value = borderless; + this.innerDefaultExpandAll.value = defaultExpandAll; + this.innerDisabled.value = disabled; + this.innerExpandIconPlacement.value = expandIconPlacement || 'left'; + if (typeof expandOnRowClick !== 'undefined') { + this.innerExpandOnRowClick.value = expandOnRowClick; + } + } + + receiveProps(newProps) { + const { value, borderless, defaultExpandAll, expandIconPlacement, expandOnRowClick, disabled } = newProps; + value !== undefined && (this.collapseValue.value = value || []); + borderless !== undefined && (this.innerBorderless.value = borderless); + defaultExpandAll !== undefined && (this.innerDefaultExpandAll.value = defaultExpandAll); + disabled !== undefined && (this.innerDisabled.value = disabled); + expandIconPlacement !== undefined && (this.innerExpandIconPlacement.value = expandIconPlacement || 'left'); + expandOnRowClick !== undefined && (this.innerExpandOnRowClick.value = expandOnRowClick); + return true; + } + + render(props: TdCollapseProps): TNode { + const { className, borderless } = props; + + const classes = classname(`${classPrefix}-collapse`, className, { + [`${classPrefix}--border-less`]: !!borderless, + }); + + return ( +
+ +
+ ); + } +} diff --git a/src/collapse/index.ts b/src/collapse/index.ts new file mode 100644 index 0000000..f78300b --- /dev/null +++ b/src/collapse/index.ts @@ -0,0 +1,10 @@ +import './style/index.js'; + +import _Collapse from './collapse'; + +export * from './type'; +export type { CollapseProps } from './collapse'; +export { default as CollapsePanel, CollapsePanelProps } from './collapse-panel'; + +export const Collapse = _Collapse; +export default Collapse; diff --git a/src/collapse/style/index.js b/src/collapse/style/index.js new file mode 100644 index 0000000..9782e45 --- /dev/null +++ b/src/collapse/style/index.js @@ -0,0 +1,9 @@ +import { css, globalCSS } from 'omi'; + +import styles from '../../_common/style/web/components/collapse/_index.less'; + +export const styleSheet = css` + ${styles} +`; + +globalCSS(styleSheet); diff --git a/src/collapse/type.ts b/src/collapse/type.ts new file mode 100644 index 0000000..fbf9518 --- /dev/null +++ b/src/collapse/type.ts @@ -0,0 +1,83 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TNode } from '../common'; + +export interface TdCollapseProps { + /** + * 类名 + */ + className?: string; + /** + * 是否为无边框模式 + * @default false + */ + borderless?: boolean; + /** + * 默认是否展开全部 + * @default false + */ + defaultExpandAll?: boolean; + /** + * 是否禁用面板展开/收起操作 + */ + disabled?: boolean; + /** + * 展开图标的位置,左侧或右侧 + * @default left + */ + expandIconPlacement?: 'left' | 'right'; + /** + * 每个面板互斥展开,每次只展开一个面板 + * @default false + */ + expandMutex?: boolean; + /** + * 是否允许点击整行标题展开面板 + * @default true + */ + expandOnRowClick?: boolean; + /** + * 展开的面板集合 + */ + value?: CollapseValue; + /** + * 切换面板时触发,返回变化的值 + */ + onChange?: (value: CollapseValue) => void; + /** + * 内容 + */ + children?: TNode; +} + +export interface TdCollapsePanelProps { + /** + * 类名 + */ + className?: string; + /** + * 当前面板处理折叠状态时,是否销毁面板内容 + * @default false + */ + destroyOnCollapse?: boolean; + /** + * 禁止当前面板展开,优先级大于 Collapse 的同名属性 + */ + disabled?: boolean; + /** + * 当前面板唯一标识,如果值为空则取当前面下标兜底作为唯一标识 + */ + value?: string | number; + /** + * 内容 + */ + children?: TNode; +} + +export type CollapseValue = Array; + +export type CollapsePanelValue = string | number; diff --git a/src/common/fake-arrow.tsx b/src/common/fake-arrow.tsx new file mode 100644 index 0000000..627e7fd --- /dev/null +++ b/src/common/fake-arrow.tsx @@ -0,0 +1,48 @@ +import { Component, tag } from 'omi'; + +import classname, { classPrefix } from '../_util/classname'; + +export interface FakeArrowProps { + /** + * 是否为激活态 + */ + isActive?: boolean; + + /** + * 是否禁用 + */ + disabled?: boolean; +} + +@tag('t-fake-arrow') +export default class FakeArrow extends Component { + componentName = `${classPrefix}-fake-arrow`; + + static css = ` +.t-fake-arrow.t-is-disabled { + cursor: not-allowed; + color: var(--td-text-color-disabled, var(--td-font-gray-4)); +} +`; + + render(props) { + const classes = classname(this.componentName, { + [`${this.componentName}--active`]: props.isActive, + [`${classPrefix}-is-disabled`]: props.disabled, + }); + + return ( + + + + ); + } +} diff --git a/src/divider/divider.tsx b/src/divider/divider.tsx index a12d5ab..cb8616c 100644 --- a/src/divider/divider.tsx +++ b/src/divider/divider.tsx @@ -1,4 +1,4 @@ -import { classNames,Component, OmiProps, tag } from 'omi'; +import { classNames, Component, OmiProps, tag } from 'omi'; import { getClassPrefix } from '../_util/classname'; import { type StyledProps } from '../common'; diff --git a/src/index.ts b/src/index.ts index 5fe7556..839ff8d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './button'; +export * from './collapse'; export * from './common'; export * from './divider'; export * from './icon';