funda-ui 4.7.197 → 4.7.202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Accordion/index.js +209 -72
- package/DragDropList/index.d.ts +1 -0
- package/DragDropList/index.js +4 -5
- package/MultipleSelect/index.js +4 -5
- package/lib/cjs/Accordion/index.js +209 -72
- package/lib/cjs/DragDropList/index.d.ts +1 -0
- package/lib/cjs/DragDropList/index.js +4 -5
- package/lib/cjs/MultipleSelect/index.js +4 -5
- package/lib/esm/Accordion/Accordion.tsx +135 -94
- package/lib/esm/Accordion/AccordionItem.tsx +118 -34
- package/lib/esm/DragDropList/index.tsx +47 -37
- package/package.json +1 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import React, { useState, useRef } from 'react';
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
import Item from './AccordionItem';
|
|
4
4
|
|
|
5
5
|
import animateStyles from 'funda-utils/dist/cjs/anim';
|
|
6
6
|
import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
// Adapt the easing parameters of TweenMax
|
|
10
9
|
export enum EasingList {
|
|
11
10
|
linear = 'linear',
|
|
@@ -15,23 +14,23 @@ export enum EasingList {
|
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
export type AccordionOptionChangeFnType = (
|
|
17
|
+
export type AccordionOptionChangeFnType = (element: HTMLDivElement, index: number) => void;
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
export type AccordionProps = {
|
|
22
21
|
wrapperClassName?: string;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** Display all items */
|
|
28
|
-
displayAllItems?: boolean;
|
|
22
|
+
/** The index of the item to be displayed by default. Set to -1 to display none, or an array of indices to display multiple items */
|
|
23
|
+
defaultActiveIndex?: number | number[];
|
|
24
|
+
/** Whether to expand all items by default */
|
|
25
|
+
defaultActiveAll?: boolean;
|
|
29
26
|
/** The number of milliseconds(ms) each iteration of the animation takes to complete */
|
|
30
27
|
duration?: number;
|
|
31
28
|
/** Types of easing animation */
|
|
32
29
|
easing?: string;
|
|
33
30
|
/** Mutually exclusive alternate expansion between the levels */
|
|
34
31
|
alternateCollapse?: boolean;
|
|
32
|
+
/** Only allow arrow to trigger the accordion */
|
|
33
|
+
arrowOnly?: boolean;
|
|
35
34
|
/** This function is called whenever the data is updated.
|
|
36
35
|
* Exposes the JSON format data about the option as an argument.
|
|
37
36
|
*/
|
|
@@ -43,142 +42,184 @@ export type AccordionProps = {
|
|
|
43
42
|
const Accordion = (props: AccordionProps) => {
|
|
44
43
|
const {
|
|
45
44
|
wrapperClassName,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
displayAllItems,
|
|
45
|
+
defaultActiveIndex,
|
|
46
|
+
defaultActiveAll = false,
|
|
49
47
|
duration,
|
|
50
48
|
easing,
|
|
51
|
-
alternateCollapse,
|
|
49
|
+
alternateCollapse = true,
|
|
50
|
+
arrowOnly = false,
|
|
52
51
|
onChange,
|
|
53
52
|
children
|
|
54
53
|
} = props;
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
const animSpeed = duration || 200;
|
|
57
56
|
const easeType: string = typeof alternateCollapse === 'undefined' ? EasingList['linear'] : EasingList[easing as never];
|
|
58
|
-
const ALTER = typeof alternateCollapse === 'undefined' ? true : alternateCollapse;
|
|
59
57
|
const rootRef = useRef<any>(null);
|
|
60
58
|
const [animOK, setAnimOK] = useState<boolean>(false);
|
|
61
59
|
const [heightObserver, setHeightObserver] = useState<number>(-1);
|
|
60
|
+
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set()); // Keep track of all expanded items
|
|
61
|
+
const animationInProgress = useRef<boolean>(false);
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
function handleClickItem(e: React.MouseEvent) {
|
|
65
|
+
if (animationInProgress.current) return;
|
|
65
66
|
if ((e.target as any).closest('.custom-accordion-header') === null) return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const animSpeed = duration || 200;
|
|
80
|
-
const $li = reactDomWrapperEl.querySelectorAll( '.custom-accordion-item' );
|
|
81
|
-
const $allContent = reactDomWrapperEl.querySelectorAll( '.custom-accordion-content__wrapper' );
|
|
82
|
-
const $curContent = reactDomEl.querySelector( '.custom-accordion-content__wrapper' );
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if ( reactDomEl.getAttribute( 'aria-expanded' ) === 'false' || reactDomEl.getAttribute( 'aria-expanded' ) === null ) {
|
|
86
|
-
|
|
67
|
+
if (animOK) return;
|
|
68
|
+
|
|
69
|
+
animationInProgress.current = true;
|
|
70
|
+
|
|
71
|
+
const reactDomEl: any = arrowOnly ? e.currentTarget.closest('.custom-accordion-item') : e.currentTarget;
|
|
72
|
+
const curIndex: number = parseInt(reactDomEl.dataset.index);
|
|
73
|
+
const reactDomWrapperEl: HTMLElement = rootRef.current;
|
|
74
|
+
const $li = reactDomWrapperEl.querySelectorAll('.custom-accordion-item');
|
|
75
|
+
const $allContent = reactDomWrapperEl.querySelectorAll('.custom-accordion-content__wrapper');
|
|
76
|
+
const $curContent = reactDomEl.querySelector('.custom-accordion-content__wrapper');
|
|
77
|
+
const $trigger = reactDomEl.querySelector('.custom-accordion-trigger');
|
|
78
|
+
|
|
79
|
+
if (reactDomEl.getAttribute('aria-expanded') === 'false' || reactDomEl.getAttribute('aria-expanded') === null) {
|
|
87
80
|
setAnimOK(true);
|
|
88
|
-
|
|
81
|
+
setTimeout(() => {
|
|
89
82
|
setAnimOK(false);
|
|
90
|
-
|
|
83
|
+
}, animSpeed);
|
|
91
84
|
|
|
92
|
-
if (
|
|
93
|
-
//Hide other all sibling
|
|
85
|
+
if (alternateCollapse) {
|
|
86
|
+
// Hide other all sibling content
|
|
94
87
|
Array.prototype.forEach.call($allContent, (node) => {
|
|
95
|
-
if (
|
|
88
|
+
if (node.clientHeight > 0) {
|
|
96
89
|
animateStyles(node, {
|
|
97
|
-
startHeight
|
|
98
|
-
endHeight
|
|
99
|
-
speed
|
|
100
|
-
} as
|
|
90
|
+
startHeight: node.scrollHeight,
|
|
91
|
+
endHeight: 0,
|
|
92
|
+
speed: animSpeed
|
|
93
|
+
} as never, easeType, () => {
|
|
94
|
+
animationInProgress.current = false;
|
|
95
|
+
});
|
|
101
96
|
}
|
|
102
|
-
|
|
103
97
|
});
|
|
104
98
|
|
|
105
|
-
//to
|
|
99
|
+
// Update all items to collapsed state
|
|
106
100
|
Array.prototype.forEach.call($li, (node) => {
|
|
107
|
-
node.
|
|
108
|
-
node.querySelector('.custom-accordion-trigger')?.classList.remove('active');
|
|
109
|
-
node.querySelector('.custom-accordion-trigger')?.classList.add('collapsed');
|
|
110
|
-
node.setAttribute( 'aria-expanded', false );
|
|
101
|
+
node.setAttribute('aria-expanded', 'false');
|
|
111
102
|
});
|
|
112
103
|
|
|
104
|
+
// Update expanded items state
|
|
105
|
+
setExpandedItems(new Set([curIndex]));
|
|
106
|
+
} else {
|
|
107
|
+
// Add current item to expanded items
|
|
108
|
+
setExpandedItems(prev => new Set([...(prev as never), curIndex]));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
reactDomEl.setAttribute('aria-expanded', 'true');
|
|
113
112
|
|
|
113
|
+
// Call onTriggerChange if it exists in the child props
|
|
114
|
+
const childProps = (children as any[])[curIndex]?.props;
|
|
115
|
+
if (typeof childProps?.onTriggerChange === 'function' && $trigger) {
|
|
116
|
+
childProps.onTriggerChange($trigger, true);
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
animateStyles($curContent, {
|
|
122
|
-
startHeight : 0,
|
|
123
|
-
endHeight : $curContent.scrollHeight,
|
|
124
|
-
speed : animSpeed
|
|
125
|
-
} as never, easeType, () => {
|
|
126
|
-
// content height observer
|
|
119
|
+
animateStyles($curContent, {
|
|
120
|
+
startHeight: 0,
|
|
121
|
+
endHeight: $curContent.scrollHeight,
|
|
122
|
+
speed: animSpeed
|
|
123
|
+
} as never, easeType, () => {
|
|
127
124
|
setHeightObserver(curIndex);
|
|
125
|
+
animationInProgress.current = false;
|
|
128
126
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if ( e.type == 'click' ) {
|
|
133
|
-
|
|
134
|
-
//to close
|
|
135
|
-
reactDomEl.classList.remove('active');
|
|
136
|
-
reactDomEl.querySelector('.custom-accordion-trigger')?.classList.remove('active');
|
|
137
|
-
reactDomEl.querySelector('.custom-accordion-trigger')?.classList.add('collapsed');
|
|
138
|
-
reactDomEl.setAttribute( 'aria-expanded', false );
|
|
139
|
-
animateStyles($curContent, {
|
|
140
|
-
startHeight : $curContent.scrollHeight,
|
|
141
|
-
endHeight : 0,
|
|
142
|
-
speed : animSpeed
|
|
143
|
-
} as never, easeType);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (typeof (onChange) === 'function') {
|
|
149
|
-
onChange(reactDomEl, Number(curIndex));
|
|
150
|
-
}
|
|
127
|
+
} else {
|
|
128
|
+
reactDomEl.setAttribute('aria-expanded', 'false');
|
|
151
129
|
|
|
130
|
+
// Call onTriggerChange if it exists in the child props
|
|
131
|
+
const childProps = (children as any[])[curIndex]?.props;
|
|
132
|
+
if (typeof childProps?.onTriggerChange === 'function' && $trigger) {
|
|
133
|
+
childProps.onTriggerChange($trigger, false);
|
|
134
|
+
}
|
|
152
135
|
|
|
153
|
-
|
|
136
|
+
// Remove current item from expanded items
|
|
137
|
+
setExpandedItems(prev => {
|
|
138
|
+
const newSet = new Set(prev);
|
|
139
|
+
newSet.delete(curIndex);
|
|
140
|
+
return newSet;
|
|
141
|
+
});
|
|
154
142
|
|
|
143
|
+
animateStyles($curContent, {
|
|
144
|
+
startHeight: $curContent.scrollHeight,
|
|
145
|
+
endHeight: 0,
|
|
146
|
+
speed: animSpeed
|
|
147
|
+
} as never, easeType, () => {
|
|
148
|
+
animationInProgress.current = false;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
onChange?.(reactDomEl, curIndex);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
// Initialize expanded items based on defaultActiveIndex or defaultActiveAll
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (defaultActiveAll && children && rootRef.current) {
|
|
159
|
+
const allIndices = Array.from({ length: (children as any[]).length }, (_, i) => i);
|
|
160
|
+
setExpandedItems(new Set(allIndices));
|
|
161
|
+
|
|
162
|
+
// Actually expand all items without animation
|
|
163
|
+
const $allItems = rootRef.current.querySelectorAll('.custom-accordion-item');
|
|
164
|
+
|
|
165
|
+
Array.prototype.forEach.call($allItems, (node, index) => {
|
|
166
|
+
node.setAttribute('aria-expanded', 'true');
|
|
167
|
+
const $curContent = node.querySelector('.custom-accordion-content__wrapper');
|
|
168
|
+
const $trigger = node.querySelector('.custom-accordion-trigger');
|
|
169
|
+
|
|
170
|
+
// Call onTriggerChange if it exists in the child props
|
|
171
|
+
const childProps = (children as any[])[index]?.props;
|
|
172
|
+
if (typeof childProps?.onTriggerChange === 'function' && $trigger) {
|
|
173
|
+
childProps.onTriggerChange($trigger, true);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Directly set height without animation
|
|
177
|
+
if ($curContent) {
|
|
178
|
+
$curContent.style.height = `${$curContent.scrollHeight}px`;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
} else if (defaultActiveIndex !== undefined) {
|
|
182
|
+
const initialExpanded = new Set<number>();
|
|
183
|
+
if (Array.isArray(defaultActiveIndex)) {
|
|
184
|
+
defaultActiveIndex.forEach(index => initialExpanded.add(index));
|
|
185
|
+
} else if (typeof defaultActiveIndex === 'number') {
|
|
186
|
+
initialExpanded.add(defaultActiveIndex);
|
|
187
|
+
}
|
|
188
|
+
setExpandedItems(initialExpanded);
|
|
189
|
+
}
|
|
190
|
+
}, [defaultActiveIndex, defaultActiveAll, children]);
|
|
191
|
+
|
|
155
192
|
return (
|
|
156
193
|
<>
|
|
157
|
-
|
|
158
194
|
<div className={combinedCls(
|
|
159
195
|
'custom-accordion-item',
|
|
160
196
|
clsWrite(wrapperClassName, 'accordion')
|
|
161
197
|
)} role="tablist" ref={rootRef}>
|
|
162
198
|
{(children != null) ? (children as any[]).map((item, i) => {
|
|
163
199
|
const childProps = { ...item.props };
|
|
164
|
-
|
|
200
|
+
let _defaultActive = false;
|
|
201
|
+
|
|
202
|
+
if (Array.isArray(defaultActiveIndex)) {
|
|
203
|
+
_defaultActive = defaultActiveIndex.includes(i);
|
|
204
|
+
} else if (typeof defaultActiveIndex === 'number') {
|
|
205
|
+
_defaultActive = defaultActiveIndex === i;
|
|
206
|
+
}
|
|
207
|
+
|
|
165
208
|
return <Item
|
|
166
209
|
key={"item" + i}
|
|
167
210
|
index={i}
|
|
211
|
+
animSpeed={animSpeed}
|
|
212
|
+
arrowOnly={arrowOnly}
|
|
168
213
|
heightObserver={heightObserver}
|
|
169
|
-
defaultActive={
|
|
170
|
-
triggerType={triggerType || 'click'}
|
|
214
|
+
defaultActive={_defaultActive}
|
|
171
215
|
onToggleEv={handleClickItem}
|
|
216
|
+
isExpanded={expandedItems.has(i)} // Both controlled and uncontrolled modes are implemented
|
|
172
217
|
{...childProps}
|
|
173
218
|
/>;
|
|
174
|
-
|
|
175
219
|
}) : null}
|
|
176
|
-
|
|
177
220
|
</div>
|
|
178
|
-
|
|
179
|
-
|
|
180
221
|
</>
|
|
181
|
-
)
|
|
222
|
+
);
|
|
182
223
|
};
|
|
183
224
|
|
|
184
225
|
export default Accordion;
|
|
@@ -5,6 +5,9 @@ import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
|
|
|
5
5
|
export type AccordionItemProps = {
|
|
6
6
|
heightObserver?: number;
|
|
7
7
|
index?: number;
|
|
8
|
+
arrowOnly?: boolean;
|
|
9
|
+
animSpeed?: number;
|
|
10
|
+
easeType?: string;
|
|
8
11
|
/** Class of items */
|
|
9
12
|
itemClassName?: string;
|
|
10
13
|
itemContentWrapperClassName?: string;
|
|
@@ -21,8 +24,10 @@ export type AccordionItemProps = {
|
|
|
21
24
|
onToggleEv?: React.MouseEventHandler<HTMLElement>;
|
|
22
25
|
/** Handling events when the animation execution is complete */
|
|
23
26
|
onTransitionEnd?: React.TransitionEventHandler<HTMLElement>;
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
/** Callback when trigger state changes, provides trigger element and expanded state */
|
|
28
|
+
onItemCollapse?: (trigger: HTMLElement, icon: HTMLElement, isExpanded: boolean) => void;
|
|
29
|
+
/** Control expanded state from parent */
|
|
30
|
+
isExpanded?: boolean;
|
|
26
31
|
/** -- */
|
|
27
32
|
children: React.ReactNode;
|
|
28
33
|
};
|
|
@@ -31,10 +36,12 @@ export type AccordionItemProps = {
|
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
const AccordionItem = (props: AccordionItemProps) => {
|
|
34
|
-
|
|
35
39
|
const {
|
|
36
40
|
heightObserver,
|
|
37
41
|
index,
|
|
42
|
+
animSpeed,
|
|
43
|
+
easeType,
|
|
44
|
+
arrowOnly,
|
|
38
45
|
itemClassName,
|
|
39
46
|
itemContentWrapperClassName,
|
|
40
47
|
itemContentClassName,
|
|
@@ -46,18 +53,60 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
46
53
|
title,
|
|
47
54
|
onToggleEv,
|
|
48
55
|
onTransitionEnd,
|
|
49
|
-
|
|
56
|
+
onItemCollapse,
|
|
57
|
+
isExpanded: controlledExpanded,
|
|
50
58
|
children,
|
|
51
59
|
...attributes
|
|
52
60
|
} = props;
|
|
53
61
|
|
|
62
|
+
const [internalExpanded, setInternalExpanded] = useState<boolean>(false);
|
|
63
|
+
const isFirstRender = useRef<boolean>(true);
|
|
64
|
+
const initialHeightSet = useRef<boolean>(false);
|
|
65
|
+
|
|
66
|
+
// Use controlled or uncontrolled expanded state
|
|
67
|
+
const isExpanded = controlledExpanded !== undefined ? controlledExpanded : internalExpanded;
|
|
54
68
|
|
|
55
|
-
|
|
56
|
-
const activedClassName = typeof(defaultActive) !== 'undefined' && defaultActive !== false ? ' active' : '';
|
|
57
69
|
const observer = useRef<ResizeObserver | null>(null);
|
|
58
70
|
const contentWrapperRef = useRef<HTMLDivElement | null>(null);
|
|
59
71
|
const contentRef = useRef<HTMLDivElement | null>(null);
|
|
60
|
-
|
|
72
|
+
const triggerRef = useRef<HTMLDivElement | HTMLButtonElement | null>(null);
|
|
73
|
+
const iconRef = useRef<HTMLSpanElement | null>(null);
|
|
74
|
+
|
|
75
|
+
const handleToggle = (e: React.MouseEvent<HTMLElement>) => {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
e.stopPropagation();
|
|
78
|
+
|
|
79
|
+
if (controlledExpanded === undefined) {
|
|
80
|
+
setInternalExpanded(prev => !prev);
|
|
81
|
+
}
|
|
82
|
+
onToggleEv?.(e);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (triggerRef.current && typeof onItemCollapse === 'function') {
|
|
87
|
+
if (isFirstRender.current) {
|
|
88
|
+
isFirstRender.current = false;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
onItemCollapse(triggerRef.current, iconRef.current as HTMLElement, isExpanded);
|
|
92
|
+
}
|
|
93
|
+
}, [isExpanded, onItemCollapse]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (contentWrapperRef.current && !initialHeightSet.current) {
|
|
97
|
+
initialHeightSet.current = true;
|
|
98
|
+
const shouldBeExpanded = typeof defaultActive !== 'undefined' && defaultActive !== false;
|
|
99
|
+
if (controlledExpanded === undefined) {
|
|
100
|
+
setInternalExpanded(shouldBeExpanded);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Set initial height when defaultActive is true
|
|
104
|
+
if (shouldBeExpanded && contentRef.current) {
|
|
105
|
+
const contentHeight = contentRef.current.offsetHeight;
|
|
106
|
+
contentWrapperRef.current.style.height = `${contentHeight}px`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}, [defaultActive, controlledExpanded]);
|
|
61
110
|
|
|
62
111
|
useEffect(() => {
|
|
63
112
|
|
|
@@ -88,6 +137,7 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
88
137
|
|
|
89
138
|
}, [heightObserver]);
|
|
90
139
|
|
|
140
|
+
|
|
91
141
|
return (
|
|
92
142
|
<>
|
|
93
143
|
|
|
@@ -97,36 +147,72 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
97
147
|
className={combinedCls(
|
|
98
148
|
'custom-accordion-item',
|
|
99
149
|
clsWrite(itemClassName, 'accordion-item'),
|
|
100
|
-
|
|
150
|
+
isExpanded ? ' active' : ''
|
|
101
151
|
)}
|
|
102
|
-
onClick={
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
aria-expanded={defaultActive ? 'true' : 'false'}
|
|
152
|
+
onClick={arrowOnly ? undefined : handleToggle}
|
|
153
|
+
onTransitionEnd={typeof onTransitionEnd === 'function' ? onTransitionEnd : undefined}
|
|
154
|
+
aria-expanded={isExpanded ? 'true' : 'false'}
|
|
106
155
|
style={typeof itemStyle !== 'undefined' ? itemStyle : {}}
|
|
156
|
+
>
|
|
157
|
+
|
|
158
|
+
<div
|
|
159
|
+
className={combinedCls(
|
|
160
|
+
'custom-accordion-header',
|
|
161
|
+
clsWrite(itemHeaderClassName, 'accordion-header position-relative')
|
|
162
|
+
)}
|
|
163
|
+
role="presentation"
|
|
107
164
|
>
|
|
108
165
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
166
|
+
{arrowOnly ? (
|
|
167
|
+
<div
|
|
168
|
+
ref={triggerRef as React.RefObject<HTMLDivElement>}
|
|
169
|
+
tabIndex={-1}
|
|
170
|
+
className={combinedCls(
|
|
171
|
+
'custom-accordion-trigger',
|
|
172
|
+
clsWrite(itemTriggerClassName, 'accordion-button'),
|
|
173
|
+
isExpanded ? 'active' : 'collapsed'
|
|
174
|
+
)}
|
|
175
|
+
>
|
|
176
|
+
{title}
|
|
177
|
+
</div>
|
|
178
|
+
) : (
|
|
179
|
+
<button
|
|
180
|
+
ref={triggerRef as React.RefObject<HTMLButtonElement>}
|
|
181
|
+
tabIndex={-1}
|
|
182
|
+
className={combinedCls(
|
|
183
|
+
'custom-accordion-trigger',
|
|
184
|
+
clsWrite(itemTriggerClassName, 'accordion-button'),
|
|
185
|
+
isExpanded ? 'active' : 'collapsed'
|
|
186
|
+
)}
|
|
187
|
+
type="button"
|
|
188
|
+
>
|
|
189
|
+
{title}
|
|
190
|
+
</button>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
<span
|
|
196
|
+
ref={iconRef}
|
|
197
|
+
onClick={!arrowOnly ? undefined : handleToggle}
|
|
198
|
+
className="custom-accordion-trigger__icon"
|
|
199
|
+
style={!arrowOnly ? {pointerEvents: 'none'} : {cursor: 'pointer'}}
|
|
200
|
+
>
|
|
201
|
+
{itemTriggerIcon}
|
|
202
|
+
</span>
|
|
203
|
+
|
|
204
|
+
|
|
122
205
|
</div>
|
|
123
|
-
<div
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
206
|
+
<div
|
|
207
|
+
ref={contentWrapperRef}
|
|
208
|
+
className={combinedCls(
|
|
209
|
+
'custom-accordion-content__wrapper w-100',
|
|
210
|
+
clsWrite(itemContentWrapperClassName, 'accordion-collapse')
|
|
211
|
+
)}
|
|
212
|
+
role="tabpanel"
|
|
213
|
+
style={{
|
|
214
|
+
height: '0',
|
|
215
|
+
overflow: 'hidden' // "overflow" affects the width, so add `w-100` to `custom-accordion-content__wrapper`
|
|
130
216
|
}}>
|
|
131
217
|
<div className={combinedCls(
|
|
132
218
|
'custom-accordion-content',
|
|
@@ -136,10 +222,8 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
136
222
|
</div>
|
|
137
223
|
</div>
|
|
138
224
|
</div>
|
|
139
|
-
|
|
140
225
|
</>
|
|
141
226
|
)
|
|
142
|
-
|
|
143
227
|
}
|
|
144
228
|
|
|
145
229
|
export default AccordionItem;
|
|
@@ -8,6 +8,7 @@ import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
|
|
|
8
8
|
import useBoundedDrag from 'funda-utils/dist/cjs/useBoundedDrag';
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
export interface ListItem {
|
|
12
13
|
id: number;
|
|
13
14
|
parentId?: number;
|
|
@@ -39,6 +40,7 @@ export interface DragDropListProps {
|
|
|
39
40
|
doubleIndent?: boolean;
|
|
40
41
|
alternateCollapse?: boolean;
|
|
41
42
|
arrow?: React.ReactNode;
|
|
43
|
+
renderOption?: (item: ListItem, dragHandleClassName: string, index: number) => React.ReactNode;
|
|
42
44
|
onUpdate?: (items: ListItem[], curId: number) => void;
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -71,6 +73,7 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
71
73
|
doubleIndent,
|
|
72
74
|
alternateCollapse,
|
|
73
75
|
arrow = <><svg viewBox="0 0 22 22" width="8px"><path d="m345.44 248.29l-194.29 194.28c-12.359 12.365-32.397 12.365-44.75 0-12.354-12.354-12.354-32.391 0-44.744l171.91-171.91-171.91-171.9c-12.354-12.359-12.354-32.394 0-44.748 12.354-12.359 32.391-12.359 44.75 0l194.29 194.28c6.177 6.18 9.262 14.271 9.262 22.366 0 8.099-3.091 16.196-9.267 22.373" transform="matrix(.03541-.00013.00013.03541 2.98 3.02)" fill="#a5a5a5" /></svg></>,
|
|
76
|
+
renderOption,
|
|
74
77
|
onUpdate,
|
|
75
78
|
...attributes
|
|
76
79
|
} = props;
|
|
@@ -83,8 +86,6 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
83
86
|
const [items, setItems] = useState<ListItem[]>([]);
|
|
84
87
|
const [editingItem, setEditingItem] = useState<number | null>(null);
|
|
85
88
|
|
|
86
|
-
const dragHandle = useRef<HTMLSpanElement | null>(null);
|
|
87
|
-
|
|
88
89
|
|
|
89
90
|
// Edit
|
|
90
91
|
const [editValue, setEditValue] = useState<Record<string, string | number>>({});
|
|
@@ -433,55 +434,64 @@ const DragDropList = forwardRef((props: DragDropListProps, externalRef: any) =>
|
|
|
433
434
|
onDoubleClick={() => handleDoubleClick(item)}
|
|
434
435
|
>
|
|
435
436
|
<div className={`${prefix}-draggable-list__itemcontent`}>
|
|
437
|
+
{renderOption ? (
|
|
438
|
+
renderOption(
|
|
439
|
+
item,
|
|
440
|
+
`${prefix}-draggable-list__handle`,
|
|
441
|
+
index
|
|
442
|
+
)
|
|
443
|
+
) : (
|
|
444
|
+
<>
|
|
445
|
+
{/** DRAG HANDLE */}
|
|
446
|
+
{/* Fix the problem that mobile terminals cannot be touched, DO NOT USE "<svg>" */}
|
|
447
|
+
{draggable && !handleHide ? <span className={`${prefix}-draggable-list__handle ${handlePos ?? 'left'}`} draggable={dragMode === 'handle'} dangerouslySetInnerHTML={{
|
|
448
|
+
__html: `${handleIcon}`
|
|
449
|
+
}}></span> : null}
|
|
450
|
+
{/** /DRAG HANDLE */}
|
|
436
451
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}}></span> : null}
|
|
442
|
-
{/** /DRAG HANDLE */}
|
|
452
|
+
{editingItem === item.id ? (
|
|
453
|
+
renderEditForm(item)
|
|
454
|
+
) : (
|
|
455
|
+
<div className={`${prefix}-draggable-list__itemcontent-inner`}>
|
|
443
456
|
|
|
444
|
-
|
|
445
|
-
renderEditForm(item)
|
|
446
|
-
) : (
|
|
447
|
-
<div className={`${prefix}-draggable-list__itemcontent-inner`}>
|
|
457
|
+
<div className={`${prefix}-draggable-list__itemlabel`}>
|
|
448
458
|
|
|
449
|
-
<div className={`${prefix}-draggable-list__itemlabel`}>
|
|
450
459
|
|
|
460
|
+
{/** LABEL */}
|
|
451
461
|
|
|
452
|
-
|
|
462
|
+
<span dangerouslySetInnerHTML={{
|
|
463
|
+
__html: `${getIndentStr(item)}${typeof item.listItemLabel === 'undefined' ? item.label : item.listItemLabel}`
|
|
464
|
+
}} />
|
|
465
|
+
{/** /LABEL */}
|
|
453
466
|
|
|
454
|
-
<span dangerouslySetInnerHTML={{
|
|
455
|
-
__html: `${getIndentStr(item)}${typeof item.listItemLabel === 'undefined' ? item.label : item.listItemLabel}`
|
|
456
|
-
}} />
|
|
457
|
-
{/** /LABEL */}
|
|
458
467
|
|
|
459
468
|
|
|
460
469
|
|
|
470
|
+
{/** COLLOPSE */}
|
|
471
|
+
{alternateCollapse && hasChildItems && (
|
|
472
|
+
<span
|
|
473
|
+
className={`${prefix}-draggable-list__collapse-arrow`}
|
|
474
|
+
onClick={(e) => handleCollapse(item.id, e)}
|
|
475
|
+
>
|
|
476
|
+
{arrow || (isCollapsed ? '▶' : '▼')}
|
|
477
|
+
</span>
|
|
478
|
+
)}
|
|
479
|
+
{/** /COLLOPSE */}
|
|
461
480
|
|
|
462
|
-
|
|
463
|
-
{alternateCollapse && hasChildItems && (
|
|
464
|
-
<span
|
|
465
|
-
className={`${prefix}-draggable-list__collapse-arrow`}
|
|
466
|
-
onClick={(e) => handleCollapse(item.id, e)}
|
|
467
|
-
>
|
|
468
|
-
{arrow || (isCollapsed ? '▶' : '▼')}
|
|
469
|
-
</span>
|
|
470
|
-
)}
|
|
471
|
-
{/** /COLLOPSE */}
|
|
481
|
+
</div>
|
|
472
482
|
|
|
473
|
-
</div>
|
|
474
483
|
|
|
484
|
+
{/** EXTENDS */}
|
|
485
|
+
{item.appendControl ? <>
|
|
486
|
+
<div className={`${prefix}-draggable-list__itemext`} id={`${prefix}-draggable-list__itemext-${item.value}`}>
|
|
487
|
+
{item.appendControl}
|
|
488
|
+
</div>
|
|
489
|
+
</> : null}
|
|
490
|
+
{/** /EXTENDS */}
|
|
475
491
|
|
|
476
|
-
{/** EXTENDS */}
|
|
477
|
-
{item.appendControl ? <>
|
|
478
|
-
<div className={`${prefix}-draggable-list__itemext`} id={`${prefix}-draggable-list__itemext-${item.value}`}>
|
|
479
|
-
{item.appendControl}
|
|
480
492
|
</div>
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
</div>
|
|
493
|
+
)}
|
|
494
|
+
</>
|
|
485
495
|
)}
|
|
486
496
|
</div>
|
|
487
497
|
</li>
|