Skip to content

Commit fb8a7b3

Browse files
committed
feat(ui): add tree component
1 parent 47679f4 commit fb8a7b3

47 files changed

Lines changed: 1358 additions & 246 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/ui/src/components/_alert-popover/AlertPopover.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React from 'react';
2-
import { useRef } from 'react';
1+
import React, { useRef } from 'react';
32

43
import { useAsync, useMount } from '../../hooks';
54

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './Popup';
2+
23
export * from './hooks';

packages/ui/src/components/_transition/CollapseTransition.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ export function DCollapseTransition(props: DCollapseTransitionProps): JSX.Elemen
7171
transitionStyle,
7272
{
7373
[dHorizontal ? 'width' : 'height']: dSize,
74-
75-
overflow: 'hidden',
74+
[dHorizontal ? 'overflowX' : 'overflowY']: 'hidden',
7675
},
7776
dHorizontal
7877
? {
@@ -93,7 +92,7 @@ export function DCollapseTransition(props: DCollapseTransitionProps): JSX.Elemen
9392
case 'entering':
9493
Object.assign(transitionStyle, {
9594
[dHorizontal ? 'width' : 'height']: dataRef.current[dHorizontal ? 'width' : 'height'],
96-
overflow: 'hidden',
95+
[dHorizontal ? 'overflowX' : 'overflowY']: 'hidden',
9796
});
9897
break;
9998

@@ -105,7 +104,7 @@ export function DCollapseTransition(props: DCollapseTransitionProps): JSX.Elemen
105104
}
106105
Object.assign(transitionStyle, {
107106
[dHorizontal ? 'width' : 'height']: dataRef.current[dHorizontal ? 'width' : 'height'],
108-
overflow: 'hidden',
107+
[dHorizontal ? 'overflowX' : 'overflowY']: 'hidden',
109108
});
110109
break;
111110

@@ -114,7 +113,7 @@ export function DCollapseTransition(props: DCollapseTransitionProps): JSX.Elemen
114113
transitionStyle,
115114
{
116115
[dHorizontal ? 'width' : 'height']: dSize,
117-
overflow: 'hidden',
116+
[dHorizontal ? 'overflowX' : 'overflowY']: 'hidden',
118117
},
119118
dHorizontal
120119
? {
@@ -135,7 +134,7 @@ export function DCollapseTransition(props: DCollapseTransitionProps): JSX.Elemen
135134
case 'leaved':
136135
Object.assign(transitionStyle, {
137136
[dHorizontal ? 'width' : 'height']: dSize,
138-
overflow: 'hidden',
137+
[dHorizontal ? 'overflowX' : 'overflowY']: 'hidden',
139138
});
140139
break;
141140

packages/ui/src/components/auto-complete/AutoComplete.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ function AutoComplete<T extends DAutoCompleteItem>(
359359
role="listbox"
360360
aria-activedescendant={isUndefined(focusItem) ? undefined : getItemId(focusItem.value)}
361361
dList={dList}
362-
dItemRender={(item, index, renderProps, parent) => {
362+
dItemRender={(item, index, { iARIA, iChildren }, parent) => {
363363
const { value: itemValue, disabled: itemDisabled, children } = item;
364364

365365
const itemNode = dCustomItem ? dCustomItem(item) : itemValue;
@@ -385,15 +385,15 @@ function AutoComplete<T extends DAutoCompleteItem>(
385385
<div className={`${dPrefix}auto-complete__option-content`}>{t('No Data')}</div>
386386
</li>
387387
) : (
388-
renderProps.children
388+
iChildren
389389
)}
390390
</ul>
391391
);
392392
}
393393

394394
return (
395395
<li
396-
{...renderProps}
396+
{...iARIA}
397397
key={itemValue}
398398
id={getItemId(itemValue)}
399399
className={getClassName(`${dPrefix}auto-complete__option`, {
@@ -423,7 +423,7 @@ function AutoComplete<T extends DAutoCompleteItem>(
423423
return 32;
424424
}}
425425
dItemNested={(item) => item.children}
426-
dCompareItem={(a, b) => a.value === b.value}
426+
dItemKey={(item) => item.value}
427427
dFocusable={canSelectItem}
428428
dFocusItem={focusItem}
429429
dSize={264}

packages/ui/src/components/auto-complete/demos/2.Group.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default function Demo() {
4141
.fill(0)
4242
.map((cn, ci) => ({
4343
value:
44-
`G${i + 1} ` +
44+
`G${i + 1}-` +
4545
Array(ci + 1)
4646
.fill(val)
4747
.join(''),

packages/ui/src/components/auto-complete/demos/3.Loading.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title:
3-
en-US: Loading
4-
zh-Hant: 加载
3+
en-US: Dynamic loading
4+
zh-Hant: 动态加载
55
---
66

77
# en-US

packages/ui/src/components/auto-complete/demos/4.Custom.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title:
3-
en-US: Customize
4-
zh-Hant: 自定义
3+
en-US: Custom display
4+
zh-Hant: 自定义显示
55
---
66

77
# en-US

packages/ui/src/components/cascader/Cascader.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import type { DId, DNestedChildren, DSize } from '../../utils/global';
22
import type { DDropdownItem } from '../dropdown';
33
import type { DFormControl } from '../form';
44
import type { DSelectItem } from '../select';
5-
import type { AbstractTreeNode } from '../tree';
5+
import type { AbstractTreeNode } from '../tree/node';
66

77
import { isNull } from 'lodash';
8-
import React, { useCallback, useState, useId, useMemo } from 'react';
8+
import React, { useCallback, useState, useId, useMemo, useRef } from 'react';
99

1010
import { usePrefixConfig, useComponentConfig, useGeneralContext, useDValue, useEventNotify } from '../../hooks';
1111
import { LoadingOutlined } from '../../icons';
@@ -14,7 +14,7 @@ import { DSelectbox } from '../_selectbox';
1414
import { DDropdown } from '../dropdown';
1515
import { useFormControl } from '../form';
1616
import { DTag } from '../tag';
17-
import { MultipleTreeNode, SingleTreeNode } from '../tree';
17+
import { MultipleTreeNode, SingleTreeNode } from '../tree/node';
1818
import { DList } from './List';
1919
import { DSearchList } from './SearchList';
2020
import { getText, TREE_NODE_KEY } from './utils';
@@ -34,8 +34,8 @@ export interface DCascaderItem<V extends DId> {
3434

3535
export interface DCascaderProps<V extends DId, T extends DCascaderItem<V>> extends React.HTMLAttributes<HTMLDivElement> {
3636
dFormControl?: DFormControl;
37-
dModel?: V | null | V[];
3837
dList: DNestedChildren<T>[];
38+
dModel?: V | null | V[];
3939
dVisible?: boolean;
4040
dPlaceholder?: string;
4141
dSize?: DSize;
@@ -59,7 +59,7 @@ export interface DCascaderProps<V extends DId, T extends DCascaderItem<V>> exten
5959
afterVisibleChange?: (visible: boolean) => void;
6060
onSearch?: (value: string) => void;
6161
onClear?: () => void;
62-
onFocusChange?: (value: V, item: DNestedChildren<T>) => void;
62+
onFirstFocus?: (value: V, item: DNestedChildren<T>) => void;
6363
}
6464

6565
const { COMPONENT_NAME } = registerComponentMate({ COMPONENT_NAME: 'DCascader' });
@@ -69,8 +69,8 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
6969
): JSX.Element | null {
7070
const {
7171
dFormControl,
72-
dModel,
7372
dList,
73+
dModel,
7474
dVisible,
7575
dPlaceholder,
7676
dSize,
@@ -91,7 +91,7 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
9191
afterVisibleChange,
9292
onSearch,
9393
onClear,
94-
onFocusChange,
94+
onFirstFocus,
9595

9696
...restProps
9797
} = useComponentConfig(COMPONENT_NAME, props);
@@ -101,6 +101,10 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
101101
const { gSize, gDisabled } = useGeneralContext();
102102
//#endregion
103103

104+
const dataRef = useRef<{
105+
focusList: Set<V>;
106+
}>({ focusList: new Set() });
107+
104108
const onKeyDown$ = useEventNotify<React.KeyboardEvent<HTMLInputElement>>();
105109

106110
const uniqueId = useId();
@@ -220,7 +224,7 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
220224
}
221225

222226
if (hasSelected) {
223-
return findNested(renderNodes as AbstractTreeNode<V, T>[], (node) => node.enabled && node.checked);
227+
return findNested(renderNodes, (node) => node.enabled && node.checked);
224228
}
225229
})();
226230

@@ -393,7 +397,10 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
393397
changeVisible(false);
394398
}}
395399
onFocusChange={(item) => {
396-
onFocusChange?.(item.value, item[TREE_NODE_KEY].origin);
400+
if (!dataRef.current.focusList.has(item.value)) {
401+
dataRef.current.focusList.add(item.value);
402+
onFirstFocus?.(item.value, item[TREE_NODE_KEY].origin);
403+
}
397404

398405
setSearchFocusItem(item);
399406
}}
@@ -416,7 +423,10 @@ function Cascader<V extends DId, T extends DCascaderItem<V>>(
416423
changeVisible(false);
417424
}}
418425
onFocusChange={(node) => {
419-
onFocusChange?.(node.id, node.origin);
426+
if (!dataRef.current.focusList.has(node.id)) {
427+
dataRef.current.focusList.add(node.id);
428+
onFirstFocus?.(node.id, node.origin);
429+
}
420430

421431
setNoSearchFocusNode(node);
422432
}}

packages/ui/src/components/cascader/List.tsx

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { DId } from '../../utils/global';
2-
import type { AbstractTreeNode, MultipleTreeNode } from '../tree';
2+
import type { AbstractTreeNode, MultipleTreeNode } from '../tree/node';
33
import type { DVirtualScrollRef } from '../virtual-scroll';
44
import type { DCascaderItem } from './Cascader';
55
import type { Subject } from 'rxjs';
@@ -171,54 +171,50 @@ export function DList<ID extends DId, T extends DCascaderItem<ID>>(props: DListP
171171
className={`${dPrefix}cascader__list`}
172172
role="listbox"
173173
aria-multiselectable={dMultiple}
174-
aria-activedescendant={dRoot && dFocusNode ? dGetItemId(dFocusNode.id) : undefined}
174+
aria-activedescendant={dRoot && !isUndefined(dFocusNode) ? dGetItemId(dFocusNode.id) : undefined}
175175
dList={dNodes}
176-
dItemRender={(item, index, renderProps) => {
177-
return (
178-
<li
179-
{...renderProps}
180-
key={item.id}
181-
id={dGetItemId(item.id)}
182-
className={getClassName(`${dPrefix}cascader__option`, {
183-
'is-focus': item.id === inFocusNode?.id,
184-
'is-selected': !dMultiple && item.checked,
185-
'is-disabled': item.disabled,
186-
})}
187-
title={item.origin.label}
188-
role="option"
189-
aria-selected={item.checked}
190-
aria-disabled={item.disabled}
191-
onClick={() => {
192-
onFocusChange(item);
193-
if (!dMultiple || item.isLeaf) {
176+
dItemRender={(item, index, { iARIA }) => (
177+
<li
178+
{...iARIA}
179+
key={item.id}
180+
id={dGetItemId(item.id)}
181+
className={getClassName(`${dPrefix}cascader__option`, {
182+
'is-focus': item.id === inFocusNode?.id,
183+
'is-selected': !dMultiple && item.checked,
184+
'is-disabled': item.disabled,
185+
})}
186+
title={item.origin.label}
187+
role="option"
188+
aria-selected={item.checked}
189+
aria-disabled={item.disabled}
190+
onClick={() => {
191+
onFocusChange(item);
192+
if (!dMultiple || item.isLeaf) {
193+
changeSelectByClick(item);
194+
}
195+
}}
196+
>
197+
{dFocusVisible && item.id === dFocusNode?.id && <div className={`${dPrefix}focus-outline`}></div>}
198+
{dMultiple && (
199+
<DCheckbox
200+
dModel={item.checked}
201+
dDisabled={item.disabled}
202+
dIndeterminate={item.indeterminate}
203+
onClick={(e) => {
204+
e.stopPropagation();
205+
onFocusChange(item);
194206
changeSelectByClick(item);
195-
}
196-
}}
197-
>
198-
{dFocusVisible && item.id === dFocusNode?.id && <div className={`${dPrefix}focus-outline`}></div>}
199-
{dMultiple && (
200-
<DCheckbox
201-
dModel={item.checked}
202-
dDisabled={item.disabled}
203-
dIndeterminate={item.indeterminate}
204-
onClick={(e) => {
205-
e.stopPropagation();
206-
onFocusChange(item);
207-
changeSelectByClick(item);
208-
}}
209-
></DCheckbox>
210-
)}
211-
<div className={`${dPrefix}cascader__option-content`}>{dCustomItem ? dCustomItem(item.origin) : item.origin.label}</div>
212-
{!item.isLeaf && (
213-
<div className={`${dPrefix}cascader__option-icon`}>
214-
{item.origin.loading ? <LoadingOutlined dSpin /> : <RightOutlined />}
215-
</div>
216-
)}
217-
</li>
218-
);
219-
}}
207+
}}
208+
></DCheckbox>
209+
)}
210+
<div className={`${dPrefix}cascader__option-content`}>{dCustomItem ? dCustomItem(item.origin) : item.origin.label}</div>
211+
{!item.isLeaf && (
212+
<div className={`${dPrefix}cascader__option-icon`}>{item.origin.loading ? <LoadingOutlined dSpin /> : <RightOutlined />}</div>
213+
)}
214+
</li>
215+
)}
220216
dItemSize={32}
221-
dCompareItem={(a, b) => a.id === b.id}
217+
dItemKey={(item) => item.id}
222218
dFocusable={(item) => item.enabled}
223219
dFocusItem={inFocusNode}
224220
dSize={264}

packages/ui/src/components/cascader/SearchList.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { DId } from '../../utils/global';
2-
import type { MultipleTreeNode } from '../tree';
2+
import type { MultipleTreeNode } from '../tree/node';
33
import type { DVirtualScrollRef } from '../virtual-scroll';
44
import type { DCascaderItem, DSearchItem } from './Cascader';
55
import type { Subject } from 'rxjs';
66

7+
import { isUndefined } from 'lodash';
78
import React, { useEffect, useRef } from 'react';
89

910
import { useEventCallback, usePrefixConfig, useTranslation } from '../../hooks';
@@ -126,9 +127,9 @@ export function DSearchList<ID extends DId, T extends DCascaderItem<ID>>(props:
126127
className={`${dPrefix}cascader__list`}
127128
role="listbox"
128129
aria-multiselectable={dMultiple}
129-
aria-activedescendant={dFocusItem ? dGetItemId(dFocusItem.value) : undefined}
130+
aria-activedescendant={isUndefined(dFocusItem) ? undefined : dGetItemId(dFocusItem.value)}
130131
dList={dList}
131-
dItemRender={(item, index, renderProps) => {
132+
dItemRender={(item, index, { iARIA }) => {
132133
const node = item[TREE_NODE_KEY];
133134
let inSelected = node.checked;
134135
if (!dOnlyLeafSelectable) {
@@ -144,7 +145,7 @@ export function DSearchList<ID extends DId, T extends DCascaderItem<ID>>(props:
144145

145146
return (
146147
<li
147-
{...renderProps}
148+
{...iARIA}
148149
key={item.value}
149150
id={dGetItemId(item.value)}
150151
className={getClassName(`${dPrefix}cascader__option`, {
@@ -167,7 +168,7 @@ export function DSearchList<ID extends DId, T extends DCascaderItem<ID>>(props:
167168
);
168169
}}
169170
dItemSize={32}
170-
dCompareItem={(a, b) => a.value === b.value}
171+
dItemKey={(item) => item.value}
171172
dFocusable={(item) => item[TREE_NODE_KEY].enabled}
172173
dFocusItem={dFocusItem}
173174
dSize={264}

0 commit comments

Comments
 (0)