funda-ui 4.7.115 → 4.7.125

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.
Files changed (43) hide show
  1. package/CascadingSelect/index.d.ts +1 -0
  2. package/CascadingSelect/index.js +7 -3
  3. package/CascadingSelectE2E/index.d.ts +1 -0
  4. package/CascadingSelectE2E/index.js +7 -3
  5. package/Date/index.js +25 -2
  6. package/EventCalendar/index.js +25 -2
  7. package/EventCalendarTimeline/index.js +25 -2
  8. package/README.md +9 -10
  9. package/SplitterPanel/index.css +63 -0
  10. package/SplitterPanel/index.d.ts +20 -0
  11. package/SplitterPanel/index.js +736 -0
  12. package/Utils/date.d.ts +15 -5
  13. package/Utils/date.js +22 -2
  14. package/Utils/time.d.ts +34 -0
  15. package/Utils/time.js +162 -0
  16. package/all.d.ts +1 -0
  17. package/all.js +1 -0
  18. package/lib/cjs/CascadingSelect/index.d.ts +1 -0
  19. package/lib/cjs/CascadingSelect/index.js +7 -3
  20. package/lib/cjs/CascadingSelectE2E/index.d.ts +1 -0
  21. package/lib/cjs/CascadingSelectE2E/index.js +7 -3
  22. package/lib/cjs/Date/index.js +25 -2
  23. package/lib/cjs/EventCalendar/index.js +25 -2
  24. package/lib/cjs/EventCalendarTimeline/index.js +25 -2
  25. package/lib/cjs/SplitterPanel/index.d.ts +20 -0
  26. package/lib/cjs/SplitterPanel/index.js +736 -0
  27. package/lib/cjs/Utils/date.d.ts +15 -5
  28. package/lib/cjs/Utils/date.js +22 -2
  29. package/lib/cjs/Utils/time.d.ts +34 -0
  30. package/lib/cjs/Utils/time.js +162 -0
  31. package/lib/cjs/index.d.ts +1 -0
  32. package/lib/cjs/index.js +1 -0
  33. package/lib/css/SplitterPanel/index.css +63 -0
  34. package/lib/esm/CascadingSelect/Group.tsx +4 -2
  35. package/lib/esm/CascadingSelect/index.tsx +3 -0
  36. package/lib/esm/CascadingSelectE2E/Group.tsx +4 -2
  37. package/lib/esm/CascadingSelectE2E/index.tsx +3 -0
  38. package/lib/esm/SplitterPanel/index.scss +82 -0
  39. package/lib/esm/SplitterPanel/index.tsx +174 -0
  40. package/lib/esm/Utils/libs/date.ts +28 -8
  41. package/lib/esm/Utils/libs/time.ts +125 -0
  42. package/lib/esm/index.js +1 -0
  43. package/package.json +1 -1
@@ -0,0 +1,174 @@
1
+ import React, { useEffect, useState, useRef, useImperativeHandle } from 'react';
2
+
3
+ import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
4
+ import useIsMobile from 'funda-utils/dist/cjs/useIsMobile';
5
+
6
+
7
+ interface SplitterPanelSubProps {
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ const SplitterPanelLeft: React.FC<SplitterPanelSubProps> = ({ children }) => <>{children}</>;
12
+ const SplitterPanelRight: React.FC<SplitterPanelSubProps> = ({ children }) => <>{children}</>;
13
+
14
+ interface SplitterPanelProps {
15
+ wrapperClassName?: string;
16
+ draggable?: boolean
17
+ initialWidth?: number;
18
+ minWidth?: number;
19
+ maxWidth?: number;
20
+ onDrag?: (type: 'dragStart' | 'dragEnd' | 'drag', leftWidth: number) => void;
21
+ children?: React.ReactNode;
22
+ }
23
+
24
+
25
+ const SplitterPanel: React.FC<SplitterPanelProps> & {
26
+ Left: typeof SplitterPanelLeft;
27
+ Right: typeof SplitterPanelRight;
28
+ } = (props) => {
29
+ const {
30
+ wrapperClassName,
31
+ draggable = true,
32
+ maxWidth,
33
+ minWidth = 100,
34
+ initialWidth = 200,
35
+ onDrag,
36
+ children
37
+ } = props;
38
+
39
+ const containerRef = useRef<HTMLDivElement>(null);
40
+ const dividerRef = useRef<HTMLDivElement>(null);
41
+ const getDefaultWidth = () => {
42
+ if (typeof initialWidth === 'number') return initialWidth;
43
+ if (typeof minWidth === 'number') return minWidth;
44
+ return 200;
45
+ };
46
+ const [leftWidth, setLeftWidth] = useState<number>(getDefaultWidth());
47
+ const isMobile = useIsMobile();
48
+
49
+ // adjust split panel
50
+ const [dragging, setDragging] = useState<boolean>(false);
51
+
52
+ // Update only when initialWidth changes and is valid, and only when it has not been dragged
53
+ const hasDragged = useRef(false);
54
+
55
+ let left: React.ReactNode = null;
56
+ let right: React.ReactNode = null;
57
+
58
+ React.Children.forEach(children, (child) => {
59
+ if (!React.isValidElement(child)) return;
60
+ if (child.type === SplitterPanelLeft) left = child.props.children;
61
+ if (child.type === SplitterPanelRight) right = child.props.children;
62
+ });
63
+
64
+
65
+
66
+ // dragdrop
67
+ function handleSplitPanelsChange(clientX: number) {
68
+ if (!containerRef.current) return;
69
+ const containerLeft = containerRef.current.getBoundingClientRect().left;
70
+ const separatorXPosition = clientX - containerLeft;
71
+ const minWidthValue = minWidth || 100;
72
+ const maxWidthValue = maxWidth || window.innerWidth / 2;
73
+ if (dragging && separatorXPosition > minWidthValue && separatorXPosition < maxWidthValue) {
74
+ setLeftWidth(separatorXPosition);
75
+ onDrag?.('drag', separatorXPosition);
76
+ hasDragged.current = true;
77
+ }
78
+ }
79
+ function handleMouseDownSplitPanels(e: React.MouseEvent) {
80
+ setDragging(true);
81
+ onDrag?.('dragStart', leftWidth ?? initialWidth);
82
+ }
83
+ function handleTouchStartSplitPanels(e: React.TouchEvent) {
84
+ setDragging(true);
85
+ onDrag?.('dragStart', leftWidth ?? initialWidth);
86
+ }
87
+
88
+ function handleMouseMoveSplitPanels(e: MouseEvent) {
89
+ handleSplitPanelsChange(e.clientX);
90
+ }
91
+ function handleTouchMoveSplitPanels(e: TouchEvent) {
92
+ handleSplitPanelsChange(e.touches[0].clientX);
93
+ }
94
+
95
+ function handleMouseUpSplitPanels() {
96
+ setDragging(false);
97
+ onDrag?.('dragEnd', leftWidth ?? initialWidth);
98
+ }
99
+
100
+
101
+ useEffect(() => {
102
+ if (!draggable) return;
103
+
104
+ // 事件监听用原生事件
105
+ document.addEventListener('mousemove', handleMouseMoveSplitPanels, false);
106
+ document.addEventListener('touchmove', handleTouchMoveSplitPanels, false);
107
+ document.addEventListener('mouseup', handleMouseUpSplitPanels, false);
108
+ document.addEventListener('touchend', handleMouseUpSplitPanels, false);
109
+ return () => {
110
+ document.removeEventListener('mousemove', handleMouseMoveSplitPanels, false);
111
+ document.removeEventListener('touchmove', handleTouchMoveSplitPanels, false);
112
+ document.removeEventListener('mouseup', handleMouseUpSplitPanels, false);
113
+ document.removeEventListener('touchend', handleMouseUpSplitPanels, false);
114
+ };
115
+ }, [draggable, dragging]);
116
+
117
+ useEffect(() => {
118
+ if (!hasDragged.current && typeof initialWidth === 'number') {
119
+ setLeftWidth(initialWidth);
120
+ }
121
+ }, [initialWidth]);
122
+
123
+ return (
124
+ <>
125
+
126
+ {isMobile ? <>
127
+ <div className={combinedCls(
128
+ 'splitter-panel-vertical',
129
+ clsWrite(wrapperClassName, '')
130
+ )}>
131
+ <div className="splitter-panel-top">{left}</div>
132
+ <div className="splitter-panel-bottom">{right}</div>
133
+ </div>
134
+
135
+ </> : <>
136
+ <div
137
+ className={combinedCls(
138
+ 'splitter-panel',
139
+ clsWrite(wrapperClassName, '')
140
+ )}
141
+ ref={containerRef}
142
+ >
143
+
144
+ <div
145
+ className="splitter-panel-left"
146
+ style={{ "--splitter-panel-left-w": `${leftWidth}px` } as React.CSSProperties}
147
+ >
148
+ {left}
149
+ </div>
150
+
151
+ {/*<!--DIVIDER-->*/}
152
+ <div
153
+ ref={dividerRef}
154
+ className={`splitter-panel-divider ${dragging ? 'dragging' : ''}`}
155
+ onMouseDown={handleMouseDownSplitPanels}
156
+ onTouchStart={handleTouchStartSplitPanels}
157
+ onTouchEnd={handleMouseUpSplitPanels}
158
+ />
159
+ {/*<!--/DIVIDER-->*/}
160
+
161
+ <div className="splitter-panel-right">
162
+ {right}
163
+ </div>
164
+ </div>
165
+ </>}
166
+
167
+ </>
168
+ )
169
+ };
170
+
171
+ SplitterPanel.Left = SplitterPanelLeft;
172
+ SplitterPanel.Right = SplitterPanelRight;
173
+
174
+ export default SplitterPanel;
@@ -205,6 +205,25 @@ function getSpecifiedDate(v: Date | string, days: number): string {
205
205
  return specifiedDay;
206
206
  }
207
207
 
208
+ /**
209
+ * Calculates the total number of days from today going back a specified number of months.
210
+ *
211
+ * @param {number} monthsAgo - The number of months to go back (e.g., 3 means the past 3 months).
212
+ * @returns {number} The total number of days between the calculated past date and today.
213
+ *
214
+ * @example
215
+ * getDaysInLastMonths(3); // Returns number of days in the past 3 months
216
+ */
217
+ function getDaysInLastMonths(monthsAgo: number = 3): number {
218
+ const today: Date = new Date();
219
+ const pastDate: Date = new Date();
220
+ pastDate.setMonth(today.getMonth() - monthsAgo);
221
+
222
+ const diffInMs: number = today.getTime() - pastDate.getTime();
223
+ const diffInDays: number = Math.round(diffInMs / (1000 * 60 * 60 * 24));
224
+
225
+ return diffInDays;
226
+ }
208
227
 
209
228
 
210
229
  /**
@@ -304,28 +323,26 @@ function getCurrentYear(): number {
304
323
  /**
305
324
  * Get current month
306
325
  * @param {Boolean} padZeroEnabled
307
- * @returns {Number}
326
+ * @returns {Number|String}
308
327
  */
309
- function getCurrentMonth(padZeroEnabled: boolean = true): number {
310
- const m: any = new Date().getMonth() + 1;
328
+ function getCurrentMonth(padZeroEnabled: boolean = true): string | number {
329
+ const m: number = new Date().getMonth() + 1;
311
330
  return padZeroEnabled ? String(m).padStart(2, '0') : m;
312
331
  }
313
332
 
314
333
 
315
-
316
334
  /**
317
335
  * Get current day
318
336
  * @param {Boolean} padZeroEnabled
319
- * @returns {Number}
337
+ * @returns {Number|String}
320
338
  */
321
- function getCurrentDay(padZeroEnabled: boolean = true): number {
322
- const d: any = new Date().getDate();
339
+ function getCurrentDay(padZeroEnabled: boolean = true): string | number {
340
+ const d: number = new Date().getDate();
323
341
  return padZeroEnabled ? String(d).padStart(2, '0') : d;
324
342
  }
325
343
 
326
344
 
327
345
 
328
-
329
346
  /**
330
347
  * Get first and last month day
331
348
  * @param {Number} v
@@ -562,7 +579,10 @@ export {
562
579
  getPrevMonthDate,
563
580
  getNextYearDate,
564
581
  getPrevYearDate,
582
+
583
+ //
565
584
  getSpecifiedDate,
585
+ getDaysInLastMonths,
566
586
 
567
587
 
568
588
  // convert
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Get timeslots from starting and ending time
3
+ * @param {string} startTime - start time in format "HH:mm"
4
+ * @param {string} endTime - end time in format "HH:mm"
5
+ * @param {number} timeInterval - time interval in minutes
6
+ * @param {boolean} formatRange - if true returns ranges like "10:00 - 11:00", if false returns single times like "10:00"
7
+ * @returns {string[]} Array of time slots
8
+ * @example
9
+
10
+ console.log(getTimeslots("10:00", "14:00", 60, true)); //['10:00 - 11:00', '11:00 - 12:00', '12:00 - 13:00', '13:00 - 14:00']
11
+ console.log(getTimeslots("10:00", "14:00", 60)); // ['10:00', '11:00', '12:00', '13:00']
12
+ */
13
+
14
+ function getTimeslots(
15
+ startTime: string,
16
+ endTime: string,
17
+ timeInterval: number,
18
+ formatRange: boolean = false
19
+ ): string[] {
20
+ const parseTime = (s: string): number => {
21
+ const c = s.split(':');
22
+ return parseInt(c[0]) * 60 + parseInt(c[1]);
23
+ }
24
+
25
+ const convertHours = (mins: number): string => {
26
+ const hour = Math.floor(mins / 60);
27
+ mins = Math.trunc(mins % 60);
28
+ const converted = pad(hour, 2) + ':' + pad(mins, 2);
29
+ return converted;
30
+ }
31
+
32
+ const pad = (str: string | number, max: number): string => {
33
+ str = str.toString();
34
+ return str.length < max ? pad("0" + str, max) : str;
35
+ }
36
+
37
+ // calculate time slot
38
+ const calculateTimeSlot = (_startTime: number, _endTime: number, _timeInterval: number): string[] => {
39
+ const timeSlots: string[] = [];
40
+ // Round start and end times to next 30 min interval
41
+ _startTime = Math.ceil(_startTime / 30) * 30;
42
+ _endTime = Math.ceil(_endTime / 30) * 30;
43
+
44
+ // Start and end of interval in the loop
45
+ let currentTime = _startTime;
46
+ while (currentTime < _endTime) {
47
+ if (formatRange) {
48
+ const t = convertHours(currentTime) + ' - ' + convertHours(currentTime + _timeInterval);
49
+ timeSlots.push(t);
50
+ } else {
51
+ timeSlots.push(convertHours(currentTime));
52
+ }
53
+ currentTime += _timeInterval;
54
+ }
55
+ return timeSlots;
56
+ }
57
+
58
+ const inputEndTime = parseTime(endTime);
59
+ const inputStartTime = parseTime(startTime);
60
+ const timeSegment = calculateTimeSlot(inputStartTime, inputEndTime, timeInterval);
61
+
62
+ return timeSegment;
63
+ }
64
+
65
+
66
+ /**
67
+ * Get minutes between two dates
68
+ * @param {Date} startDate - start date
69
+ * @param {Date} endDate - ebd date
70
+ * @returns Number
71
+ */
72
+ function getMinutesBetweenDates(startDate, endDate) {
73
+ const diff = endDate.getTime() - startDate.getTime();
74
+ return (diff / 60000);
75
+ }
76
+
77
+
78
+ /**
79
+ * Get minutes between two time
80
+ * @param {String} startTime - start time
81
+ * @param {String} endTime - ebd time
82
+ * @returns Number
83
+ */
84
+ function getMinutesBetweenTime(startTime, endTime) {
85
+ const pad = (num) => {
86
+ return ("0" + num).slice(-2);
87
+ };
88
+ let s = startTime.split(":"), sMin = +s[1] + s[0] * 60,
89
+ e = endTime.split(":"), eMin = +e[1] + e[0] * 60,
90
+ diff = eMin - sMin;
91
+
92
+ if (diff < 0) { sMin -= 12 * 60; diff = eMin - sMin }
93
+ const h = Math.floor(diff / 60),
94
+ m = diff % 60;
95
+ return "" + pad(h) + ":" + pad(m);
96
+ }
97
+
98
+
99
+
100
+ /**
101
+ * Convert HH:MM:SS into minute
102
+ * @param {String} timeStr - time string
103
+ * @returns Number
104
+ */
105
+ function convertTimeToMin(timeStr) {
106
+ const _time = timeStr.split(':').length === 3 ? `${timeStr}` : `${timeStr}:00`;
107
+
108
+ const res = _time.split(':'); // split it at the colons
109
+
110
+ // Hours are worth 60 minutes.
111
+ const minutes = (+res[0]) * 60 + (+res[1]);
112
+ return minutes;
113
+ }
114
+
115
+
116
+ export {
117
+ getTimeslots,
118
+ getMinutesBetweenDates,
119
+ getMinutesBetweenTime,
120
+ convertTimeToMin
121
+ };
122
+
123
+
124
+
125
+
package/lib/esm/index.js CHANGED
@@ -33,6 +33,7 @@ export { default as Scrollbar } from './Scrollbar';
33
33
  export { default as SearchBar } from './SearchBar';
34
34
  export { default as Select } from './Select';
35
35
  export { default as ShowMoreLess } from './ShowMoreLess';
36
+ export { default as SplitterPanel } from './SplitterPanel';
36
37
  export { default as Stepper } from './Stepper';
37
38
  export { default as Switch } from './Switch';
38
39
  export { default as Table } from './Table';
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.115",
5
+ "version": "4.7.125",
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",