funda-ui 4.7.545 → 4.7.555
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 +71 -660
- package/lib/cjs/Accordion/index.js +71 -660
- package/lib/esm/Accordion/Accordion.tsx +44 -79
- package/lib/esm/Accordion/AccordionItem.tsx +37 -19
- package/lib/esm/Accordion/index.tsx +1 -1
- package/package.json +1 -1
|
@@ -2,13 +2,12 @@ import React, { useState, useRef, useEffect } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import Item from './AccordionItem';
|
|
4
4
|
|
|
5
|
-
import animateStyles from 'funda-utils/dist/cjs/anim';
|
|
6
5
|
import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
|
|
7
6
|
|
|
8
|
-
|
|
9
7
|
// Adapt the easing parameters of TweenMax
|
|
10
8
|
export enum EasingList {
|
|
11
9
|
linear = 'linear',
|
|
10
|
+
ease = 'ease',
|
|
12
11
|
easeIn = 'ease-in',
|
|
13
12
|
easeOut = 'ease-out',
|
|
14
13
|
easeInOut = 'ease-in-out'
|
|
@@ -18,6 +17,7 @@ export enum EasingList {
|
|
|
18
17
|
export type AccordionOptionChangeFnType = (element: HTMLDivElement, index: number) => void;
|
|
19
18
|
|
|
20
19
|
|
|
20
|
+
|
|
21
21
|
export type AccordionProps = {
|
|
22
22
|
wrapperClassName?: string;
|
|
23
23
|
/** 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 */
|
|
@@ -46,7 +46,7 @@ const Accordion = (props: AccordionProps) => {
|
|
|
46
46
|
defaultActiveIndex,
|
|
47
47
|
defaultActiveAll = false,
|
|
48
48
|
duration,
|
|
49
|
-
easing,
|
|
49
|
+
easing = 'ease',
|
|
50
50
|
alternateCollapse = true,
|
|
51
51
|
arrowOnly = false,
|
|
52
52
|
onChange,
|
|
@@ -56,17 +56,16 @@ const Accordion = (props: AccordionProps) => {
|
|
|
56
56
|
const animSpeed = duration || 200;
|
|
57
57
|
const easeType: string = typeof alternateCollapse === 'undefined' ? EasingList['linear'] : EasingList[easing as never];
|
|
58
58
|
const rootRef = useRef<any>(null);
|
|
59
|
-
const
|
|
60
|
-
const [animOK, setAnimOK] = useState<boolean>(false);
|
|
61
|
-
const [heightObserver, setHeightObserver] = useState<number>(-1);
|
|
59
|
+
const [heightObserver, setHeightObserver] = useState<number[]>([]);
|
|
62
60
|
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set()); // Keep track of all expanded items
|
|
63
61
|
const animationInProgress = useRef<boolean>(false);
|
|
62
|
+
const initExpanded = useRef<boolean>(false);
|
|
63
|
+
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
function handleClickItem(e: React.MouseEvent) {
|
|
67
67
|
if (animationInProgress.current) return;
|
|
68
68
|
if ((e.target as any).closest('.custom-accordion-header') === null) return;
|
|
69
|
-
if (animOK) return;
|
|
70
69
|
|
|
71
70
|
animationInProgress.current = true;
|
|
72
71
|
|
|
@@ -74,34 +73,10 @@ const Accordion = (props: AccordionProps) => {
|
|
|
74
73
|
const curIndex: number = parseInt(reactDomEl.dataset.index);
|
|
75
74
|
const reactDomWrapperEl: HTMLElement = rootRef.current;
|
|
76
75
|
const $li = reactDomWrapperEl.querySelectorAll('.custom-accordion-item');
|
|
77
|
-
const $allContent = reactDomWrapperEl.querySelectorAll('.custom-accordion-content__wrapper');
|
|
78
|
-
const $curContent = reactDomEl.querySelector('.custom-accordion-content__wrapper');
|
|
79
76
|
|
|
80
77
|
if (reactDomEl.getAttribute('aria-expanded') === 'false' || reactDomEl.getAttribute('aria-expanded') === null) {
|
|
81
|
-
setAnimOK(true);
|
|
82
|
-
setTimeout(() => {
|
|
83
|
-
setAnimOK(false);
|
|
84
|
-
}, animSpeed);
|
|
85
|
-
|
|
86
78
|
if (alternateCollapse) {
|
|
87
|
-
|
|
88
|
-
Array.prototype.forEach.call($allContent, (node) => {
|
|
89
|
-
if (node.clientHeight > 0) {
|
|
90
|
-
animateStyles(node, {
|
|
91
|
-
startHeight: node.scrollHeight,
|
|
92
|
-
endHeight: 0,
|
|
93
|
-
speed: animSpeed
|
|
94
|
-
} as never, easeType, () => {
|
|
95
|
-
animationInProgress.current = false;
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Update all items to collapsed state
|
|
101
|
-
Array.prototype.forEach.call($li, (node) => {
|
|
102
|
-
node.setAttribute('aria-expanded', 'false');
|
|
103
|
-
});
|
|
104
|
-
|
|
79
|
+
|
|
105
80
|
// Update expanded items state
|
|
106
81
|
setExpandedItems(new Set([curIndex]));
|
|
107
82
|
} else {
|
|
@@ -109,82 +84,71 @@ const Accordion = (props: AccordionProps) => {
|
|
|
109
84
|
setExpandedItems(prev => new Set([...(prev as never), curIndex]));
|
|
110
85
|
}
|
|
111
86
|
|
|
112
|
-
reactDomEl.setAttribute('aria-expanded', 'true');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
animateStyles($curContent, {
|
|
116
|
-
startHeight: 0,
|
|
117
|
-
endHeight: $curContent.scrollHeight,
|
|
118
|
-
speed: animSpeed
|
|
119
|
-
} as never, easeType, () => {
|
|
120
|
-
setHeightObserver(curIndex);
|
|
121
|
-
animationInProgress.current = false;
|
|
122
|
-
});
|
|
123
87
|
} else {
|
|
124
|
-
|
|
125
|
-
|
|
88
|
+
|
|
126
89
|
// Remove current item from expanded items
|
|
127
90
|
setExpandedItems(prev => {
|
|
128
91
|
const newSet = new Set(prev);
|
|
129
92
|
newSet.delete(curIndex);
|
|
130
93
|
return newSet;
|
|
131
94
|
});
|
|
132
|
-
|
|
133
|
-
animateStyles($curContent, {
|
|
134
|
-
startHeight: $curContent.scrollHeight,
|
|
135
|
-
endHeight: 0,
|
|
136
|
-
speed: animSpeed
|
|
137
|
-
} as never, easeType, () => {
|
|
138
|
-
animationInProgress.current = false;
|
|
139
|
-
});
|
|
140
95
|
}
|
|
141
96
|
|
|
97
|
+
// Reset animation flag after transition duration
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
animationInProgress.current = false;
|
|
100
|
+
}, animSpeed);
|
|
101
|
+
|
|
142
102
|
onChange?.(reactDomEl, curIndex);
|
|
143
103
|
}
|
|
144
104
|
|
|
145
105
|
|
|
146
|
-
//
|
|
106
|
+
//Initialize expanded items based on defaultActiveIndex or defaultActiveAll
|
|
147
107
|
useEffect(() => {
|
|
148
|
-
|
|
108
|
+
|
|
109
|
+
// Skip the default height initialization to prevent re-initialization.
|
|
110
|
+
// This method is useful when you want to manually control when the accordion should
|
|
111
|
+
// skip the automatic height initialization process, especially useful when `children` change frequently.
|
|
112
|
+
if (initExpanded.current === true) return;
|
|
113
|
+
|
|
114
|
+
if (defaultActiveAll && children && rootRef.current) {
|
|
149
115
|
const allIndices = Array.from({ length: (children as any[]).length }, (_, i) => i);
|
|
150
116
|
setExpandedItems(new Set(allIndices));
|
|
117
|
+
// Set heightObserver for all items to enable height monitoring
|
|
118
|
+
setHeightObserver(allIndices);
|
|
151
119
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
Array.prototype.forEach.call($allItems, (node, index) => {
|
|
155
|
-
const $curContent = node.querySelector('.custom-accordion-content__wrapper');
|
|
156
|
-
if ($curContent) {
|
|
157
|
-
// !!! Don't use the .custom-accordion-contentwrapper height directly, it may be more than a dozen pixels
|
|
158
|
-
$curContent.style.height = `${node.querySelector('.custom-accordion-content__wrapper > .custom-accordion-content').scrollHeight}px`;
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
animateStyles(animPlaceholderRef.current as HTMLDivElement, {
|
|
164
|
-
startHeight: 0,
|
|
165
|
-
endHeight: 10,
|
|
166
|
-
speed: animSpeed
|
|
167
|
-
} as never, easeType, () => {
|
|
168
|
-
setTimeout(() => {
|
|
169
|
-
|
|
170
|
-
}, animSpeed);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
|
|
120
|
+
//
|
|
121
|
+
initExpanded.current = true;
|
|
174
122
|
} else if (defaultActiveIndex !== undefined) {
|
|
175
123
|
const initialExpanded = new Set<number>();
|
|
124
|
+
const observerIndices: number[] = [];
|
|
125
|
+
|
|
176
126
|
if (Array.isArray(defaultActiveIndex)) {
|
|
177
|
-
defaultActiveIndex.forEach(index =>
|
|
127
|
+
defaultActiveIndex.forEach(index => {
|
|
128
|
+
initialExpanded.add(index);
|
|
129
|
+
observerIndices.push(index);
|
|
130
|
+
});
|
|
178
131
|
} else if (typeof defaultActiveIndex === 'number') {
|
|
179
132
|
initialExpanded.add(defaultActiveIndex);
|
|
133
|
+
observerIndices.push(defaultActiveIndex);
|
|
180
134
|
}
|
|
135
|
+
|
|
181
136
|
setExpandedItems(initialExpanded);
|
|
137
|
+
// Set heightObserver for default active items to enable height monitoring
|
|
138
|
+
if (observerIndices.length > 0) {
|
|
139
|
+
setHeightObserver(observerIndices);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//
|
|
143
|
+
initExpanded.current = true;
|
|
144
|
+
|
|
182
145
|
}
|
|
146
|
+
|
|
183
147
|
}, [defaultActiveIndex, defaultActiveAll, children]);
|
|
184
148
|
|
|
149
|
+
|
|
185
150
|
return (
|
|
186
151
|
<>
|
|
187
|
-
<div className="custom-accordion-anim-placeholder" style={{display: 'none'}} ref={animPlaceholderRef}></div>
|
|
188
152
|
<div className={combinedCls(
|
|
189
153
|
'custom-accordion-item',
|
|
190
154
|
clsWrite(wrapperClassName, 'accordion')
|
|
@@ -204,6 +168,7 @@ const Accordion = (props: AccordionProps) => {
|
|
|
204
168
|
index={i}
|
|
205
169
|
defaultActiveAll={defaultActiveAll}
|
|
206
170
|
animSpeed={animSpeed}
|
|
171
|
+
easing={easeType}
|
|
207
172
|
arrowOnly={arrowOnly}
|
|
208
173
|
heightObserver={heightObserver}
|
|
209
174
|
activeItem={_defaultActive}
|
|
@@ -4,11 +4,11 @@ import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
export type AccordionItemProps = {
|
|
7
|
-
heightObserver?: number;
|
|
7
|
+
heightObserver?: number[];
|
|
8
8
|
index?: number;
|
|
9
9
|
arrowOnly?: boolean;
|
|
10
10
|
animSpeed?: number;
|
|
11
|
-
|
|
11
|
+
easing?: string;
|
|
12
12
|
/** Class of items */
|
|
13
13
|
itemClassName?: string;
|
|
14
14
|
itemContentWrapperClassName?: string;
|
|
@@ -41,7 +41,7 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
41
41
|
heightObserver,
|
|
42
42
|
index,
|
|
43
43
|
animSpeed,
|
|
44
|
-
|
|
44
|
+
easing,
|
|
45
45
|
arrowOnly,
|
|
46
46
|
itemClassName,
|
|
47
47
|
itemContentWrapperClassName,
|
|
@@ -83,6 +83,19 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
83
83
|
onToggleEv?.(e);
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
+
|
|
87
|
+
// Generate CSS transition style
|
|
88
|
+
const getTransitionStyle = () => {
|
|
89
|
+
const transitionDuration = `${animSpeed}ms`;
|
|
90
|
+
const transitionTiming = easing;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
transition: `max-height ${transitionDuration} ${transitionTiming}`,
|
|
94
|
+
overflow: 'hidden'
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
|
|
86
99
|
useEffect(() => {
|
|
87
100
|
if (triggerRef.current && typeof onItemCollapse === 'function') {
|
|
88
101
|
if (isFirstRender.current) {
|
|
@@ -91,7 +104,7 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
91
104
|
}
|
|
92
105
|
onItemCollapse(triggerRef.current, iconRef.current as HTMLElement, isExpanded);
|
|
93
106
|
}
|
|
94
|
-
}, [isExpanded
|
|
107
|
+
}, [isExpanded]);
|
|
95
108
|
|
|
96
109
|
useEffect(() => {
|
|
97
110
|
if (contentWrapperRef.current && !initialHeightSet.current) {
|
|
@@ -101,47 +114,55 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
101
114
|
setInternalExpanded(shouldBeExpanded);
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
// Set initial
|
|
117
|
+
// Set initial maxHeight when activeItem is true
|
|
105
118
|
if (shouldBeExpanded && contentRef.current) {
|
|
106
119
|
const contentHeight = contentRef.current.offsetHeight;
|
|
107
|
-
contentWrapperRef.current.style.
|
|
120
|
+
contentWrapperRef.current.style.maxHeight = `${contentHeight}px`;
|
|
108
121
|
}
|
|
109
122
|
}
|
|
110
123
|
}, [activeItem, controlledExpanded]);
|
|
111
124
|
|
|
112
125
|
useEffect(() => {
|
|
126
|
+
if (!heightObserver || !Array.isArray(heightObserver) || !heightObserver.includes(index as number)) return;
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// When the content height changes dynamically, change the height of the wrapper
|
|
128
|
+
// When the content height changes dynamically, change the maxHeight of the wrapper
|
|
117
129
|
if (contentRef.current && contentWrapperRef.current) {
|
|
118
130
|
const _contentPadding = window.getComputedStyle(contentRef.current as HTMLDivElement).getPropertyValue('padding-bottom');
|
|
119
131
|
|
|
120
132
|
observer.current = new ResizeObserver(entries => {
|
|
121
|
-
|
|
122
133
|
if (!Array.isArray(entries) || !entries.length) {
|
|
123
134
|
return;
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
entries.forEach(entry => {
|
|
127
|
-
if (contentWrapperRef.current !== null
|
|
138
|
+
if (contentWrapperRef.current !== null && isExpanded) {
|
|
139
|
+
(contentWrapperRef.current as HTMLDivElement).style.maxHeight = entry.contentRect.bottom + parseFloat(_contentPadding) + 'px';
|
|
140
|
+
}
|
|
128
141
|
});
|
|
129
142
|
});
|
|
130
143
|
observer.current.observe(contentRef.current);
|
|
131
|
-
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
return () => {
|
|
135
147
|
if (contentRef.current) observer.current?.unobserve(contentRef.current);
|
|
136
148
|
};
|
|
137
149
|
|
|
150
|
+
}, [heightObserver, isExpanded]);
|
|
138
151
|
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
// Update maxHeight when expanded state changes
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (contentWrapperRef.current && contentRef.current) {
|
|
155
|
+
if (isExpanded) {
|
|
156
|
+
const contentHeight = contentRef.current.scrollHeight;
|
|
157
|
+
contentWrapperRef.current.style.maxHeight = `${contentHeight}px`;
|
|
158
|
+
} else {
|
|
159
|
+
contentWrapperRef.current.style.maxHeight = '0px';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}, [isExpanded]);
|
|
141
163
|
|
|
142
164
|
return (
|
|
143
165
|
<>
|
|
144
|
-
|
|
145
166
|
<div
|
|
146
167
|
{...attributes}
|
|
147
168
|
data-index={index}
|
|
@@ -211,10 +232,7 @@ const AccordionItem = (props: AccordionItemProps) => {
|
|
|
211
232
|
clsWrite(itemContentWrapperClassName, 'accordion-collapse')
|
|
212
233
|
)}
|
|
213
234
|
role="tabpanel"
|
|
214
|
-
style={
|
|
215
|
-
height: '0',
|
|
216
|
-
overflow: 'hidden' // "overflow" affects the width, so add `w-100` to `custom-accordion-content__wrapper`
|
|
217
|
-
}}>
|
|
235
|
+
style={getTransitionStyle()}>
|
|
218
236
|
<div className={combinedCls(
|
|
219
237
|
'custom-accordion-content',
|
|
220
238
|
clsWrite(itemContentClassName, 'accordion-body')
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as Accordion } from './Accordion';
|
|
2
|
-
export { default as AccordionItem } from './AccordionItem';
|
|
2
|
+
export { default as AccordionItem } from './AccordionItem';
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "UIUX Lab",
|
|
3
3
|
"email": "uiuxlab@gmail.com",
|
|
4
4
|
"name": "funda-ui",
|
|
5
|
-
"version": "4.7.
|
|
5
|
+
"version": "4.7.555",
|
|
6
6
|
"description": "React components using pure Bootstrap 5+ which does not contain any external style and script libraries.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|