@thangdevalone/meet-layout-grid-react 1.0.8 → 1.1.0
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/README.md +4 -5
- package/dist/index.cjs +145 -10
- package/dist/index.d.cts +99 -14
- package/dist/index.d.mts +99 -14
- package/dist/index.d.ts +99 -14
- package/dist/index.mjs +146 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -43,7 +43,6 @@ Wraps the grid, computes layout, provides context.
|
|
|
43
43
|
gap={8}
|
|
44
44
|
count={6}
|
|
45
45
|
layoutMode="gallery"
|
|
46
|
-
speakerIndex={0}
|
|
47
46
|
pinnedIndex={0}
|
|
48
47
|
sidebarPosition="right"
|
|
49
48
|
sidebarRatio={0.25}
|
|
@@ -85,7 +84,8 @@ const grid = useMeetGrid({
|
|
|
85
84
|
count: 6,
|
|
86
85
|
aspectRatio: '16:9',
|
|
87
86
|
gap: 8,
|
|
88
|
-
layoutMode: '
|
|
87
|
+
layoutMode: 'sidebar',
|
|
88
|
+
sidebarPosition: 'bottom', // speaker-like layout
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
const { top, left } = grid.getPosition(index)
|
|
@@ -94,10 +94,9 @@ const { width, height } = grid.getItemDimensions(index)
|
|
|
94
94
|
|
|
95
95
|
## Layout modes
|
|
96
96
|
|
|
97
|
-
- **gallery** — Same-size tiles
|
|
98
|
-
- **speaker** — One large tile
|
|
97
|
+
- **gallery** — Same-size tiles (use `pinnedIndex` to pin a participant)
|
|
99
98
|
- **spotlight** — Single participant
|
|
100
|
-
- **sidebar** — Main + thumbnails
|
|
99
|
+
- **sidebar** — Main + thumbnails (use `sidebarPosition: 'bottom'` for speaker-like layout)
|
|
101
100
|
|
|
102
101
|
## Animation presets
|
|
103
102
|
|
package/dist/index.cjs
CHANGED
|
@@ -46,6 +46,7 @@ function useGridDimensions(ref) {
|
|
|
46
46
|
return dimensions;
|
|
47
47
|
}
|
|
48
48
|
function useMeetGrid(options) {
|
|
49
|
+
const itemAspectRatiosKey = options.itemAspectRatios?.join(",") ?? "";
|
|
49
50
|
return React.useMemo(() => {
|
|
50
51
|
return meetLayoutGridCore.createMeetGrid(options);
|
|
51
52
|
}, [
|
|
@@ -56,13 +57,14 @@ function useMeetGrid(options) {
|
|
|
56
57
|
options.gap,
|
|
57
58
|
options.layoutMode,
|
|
58
59
|
options.pinnedIndex,
|
|
59
|
-
options.speakerIndex,
|
|
60
60
|
options.sidebarPosition,
|
|
61
61
|
options.sidebarRatio,
|
|
62
62
|
options.maxItemsPerPage,
|
|
63
63
|
options.currentPage,
|
|
64
|
-
options.
|
|
65
|
-
options.
|
|
64
|
+
options.maxVisible,
|
|
65
|
+
options.currentVisiblePage,
|
|
66
|
+
options.flexLayout,
|
|
67
|
+
itemAspectRatiosKey
|
|
66
68
|
]);
|
|
67
69
|
}
|
|
68
70
|
function useGridAnimation(preset = "smooth") {
|
|
@@ -77,7 +79,6 @@ const GridContainer = React.forwardRef(
|
|
|
77
79
|
count,
|
|
78
80
|
layoutMode = "gallery",
|
|
79
81
|
pinnedIndex,
|
|
80
|
-
speakerIndex,
|
|
81
82
|
sidebarPosition,
|
|
82
83
|
sidebarRatio,
|
|
83
84
|
springPreset = "smooth",
|
|
@@ -85,8 +86,10 @@ const GridContainer = React.forwardRef(
|
|
|
85
86
|
className,
|
|
86
87
|
maxItemsPerPage,
|
|
87
88
|
currentPage,
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
maxVisible,
|
|
90
|
+
currentVisiblePage,
|
|
91
|
+
itemAspectRatios,
|
|
92
|
+
flexLayout,
|
|
90
93
|
...props
|
|
91
94
|
}, forwardedRef) {
|
|
92
95
|
const internalRef = React.useRef(null);
|
|
@@ -100,13 +103,14 @@ const GridContainer = React.forwardRef(
|
|
|
100
103
|
gap,
|
|
101
104
|
layoutMode,
|
|
102
105
|
pinnedIndex,
|
|
103
|
-
speakerIndex,
|
|
104
106
|
sidebarPosition,
|
|
105
107
|
sidebarRatio,
|
|
106
108
|
maxItemsPerPage,
|
|
107
109
|
currentPage,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
maxVisible,
|
|
111
|
+
currentVisiblePage,
|
|
112
|
+
itemAspectRatios,
|
|
113
|
+
flexLayout
|
|
110
114
|
};
|
|
111
115
|
const grid = useMeetGrid(gridOptions);
|
|
112
116
|
const containerStyle = {
|
|
@@ -123,6 +127,7 @@ const GridItem = React.forwardRef(
|
|
|
123
127
|
function GridItem2({
|
|
124
128
|
index,
|
|
125
129
|
children,
|
|
130
|
+
itemAspectRatio,
|
|
126
131
|
transition: customTransition,
|
|
127
132
|
disableAnimation = false,
|
|
128
133
|
className,
|
|
@@ -138,6 +143,7 @@ const GridItem = React.forwardRef(
|
|
|
138
143
|
}
|
|
139
144
|
const { top, left } = grid.getPosition(index);
|
|
140
145
|
const { width, height } = grid.getItemDimensions(index);
|
|
146
|
+
const contentDimensions = grid.getItemContentDimensions(index, itemAspectRatio);
|
|
141
147
|
const isMain = grid.isMainItem(index);
|
|
142
148
|
if (grid.layoutMode === "spotlight" && !isMain) {
|
|
143
149
|
return null;
|
|
@@ -155,6 +161,15 @@ const GridItem = React.forwardRef(
|
|
|
155
161
|
top,
|
|
156
162
|
left
|
|
157
163
|
};
|
|
164
|
+
const lastVisibleOthersIndex = grid.getLastVisibleOthersIndex();
|
|
165
|
+
const isLastVisibleOther = index === lastVisibleOthersIndex;
|
|
166
|
+
const hiddenCount = grid.hiddenCount;
|
|
167
|
+
const renderChildren = () => {
|
|
168
|
+
if (typeof children === "function") {
|
|
169
|
+
return children({ contentDimensions, isLastVisibleOther, hiddenCount });
|
|
170
|
+
}
|
|
171
|
+
return children;
|
|
172
|
+
};
|
|
158
173
|
if (disableAnimation) {
|
|
159
174
|
return /* @__PURE__ */ React__default.createElement(
|
|
160
175
|
"div",
|
|
@@ -166,7 +181,7 @@ const GridItem = React.forwardRef(
|
|
|
166
181
|
"data-grid-main": isMain,
|
|
167
182
|
...props
|
|
168
183
|
},
|
|
169
|
-
|
|
184
|
+
renderChildren()
|
|
170
185
|
);
|
|
171
186
|
}
|
|
172
187
|
return /* @__PURE__ */ React__default.createElement(
|
|
@@ -183,6 +198,125 @@ const GridItem = React.forwardRef(
|
|
|
183
198
|
"data-grid-main": isMain,
|
|
184
199
|
...props
|
|
185
200
|
},
|
|
201
|
+
renderChildren()
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
const FloatingGridItem = React.forwardRef(
|
|
206
|
+
function FloatingGridItem2({
|
|
207
|
+
children,
|
|
208
|
+
width = 120,
|
|
209
|
+
height = 160,
|
|
210
|
+
initialPosition = { x: 16, y: 16 },
|
|
211
|
+
anchor: initialAnchor = "bottom-right",
|
|
212
|
+
visible = true,
|
|
213
|
+
edgePadding = 12,
|
|
214
|
+
onAnchorChange,
|
|
215
|
+
transition,
|
|
216
|
+
borderRadius = 12,
|
|
217
|
+
boxShadow = "0 4px 20px rgba(0,0,0,0.3)",
|
|
218
|
+
className,
|
|
219
|
+
style,
|
|
220
|
+
...props
|
|
221
|
+
}, ref) {
|
|
222
|
+
const { dimensions } = useGridContext();
|
|
223
|
+
const [currentAnchor, setCurrentAnchor] = React__default.useState(initialAnchor);
|
|
224
|
+
const x = react.useMotionValue(0);
|
|
225
|
+
const y = react.useMotionValue(0);
|
|
226
|
+
const [isInitialized, setIsInitialized] = React__default.useState(false);
|
|
227
|
+
const getCornerPosition = React__default.useCallback((corner) => {
|
|
228
|
+
const padding = edgePadding + initialPosition.x;
|
|
229
|
+
switch (corner) {
|
|
230
|
+
case "top-left":
|
|
231
|
+
return { x: padding, y: padding };
|
|
232
|
+
case "top-right":
|
|
233
|
+
return { x: dimensions.width - width - padding, y: padding };
|
|
234
|
+
case "bottom-left":
|
|
235
|
+
return { x: padding, y: dimensions.height - height - padding };
|
|
236
|
+
case "bottom-right":
|
|
237
|
+
default:
|
|
238
|
+
return { x: dimensions.width - width - padding, y: dimensions.height - height - padding };
|
|
239
|
+
}
|
|
240
|
+
}, [dimensions.width, dimensions.height, width, height, edgePadding, initialPosition.x]);
|
|
241
|
+
React__default.useEffect(() => {
|
|
242
|
+
if (dimensions.width > 0 && dimensions.height > 0 && !isInitialized) {
|
|
243
|
+
const pos = getCornerPosition(currentAnchor);
|
|
244
|
+
x.set(pos.x);
|
|
245
|
+
y.set(pos.y);
|
|
246
|
+
setIsInitialized(true);
|
|
247
|
+
}
|
|
248
|
+
}, [dimensions.width, dimensions.height, currentAnchor, getCornerPosition, isInitialized, x, y]);
|
|
249
|
+
React__default.useEffect(() => {
|
|
250
|
+
if (isInitialized && dimensions.width > 0 && dimensions.height > 0) {
|
|
251
|
+
const pos = getCornerPosition(currentAnchor);
|
|
252
|
+
const springConfig = { type: "spring", stiffness: 400, damping: 30 };
|
|
253
|
+
react.animate(x, pos.x, springConfig);
|
|
254
|
+
react.animate(y, pos.y, springConfig);
|
|
255
|
+
}
|
|
256
|
+
}, [currentAnchor, dimensions.width, dimensions.height, getCornerPosition, isInitialized, x, y]);
|
|
257
|
+
if (!visible || dimensions.width === 0 || dimensions.height === 0)
|
|
258
|
+
return null;
|
|
259
|
+
const findNearestCorner = (posX, posY) => {
|
|
260
|
+
const centerX = posX + width / 2;
|
|
261
|
+
const centerY = posY + height / 2;
|
|
262
|
+
const containerCenterX = dimensions.width / 2;
|
|
263
|
+
const containerCenterY = dimensions.height / 2;
|
|
264
|
+
const isLeft = centerX < containerCenterX;
|
|
265
|
+
const isTop = centerY < containerCenterY;
|
|
266
|
+
if (isTop && isLeft)
|
|
267
|
+
return "top-left";
|
|
268
|
+
if (isTop && !isLeft)
|
|
269
|
+
return "top-right";
|
|
270
|
+
if (!isTop && isLeft)
|
|
271
|
+
return "bottom-left";
|
|
272
|
+
return "bottom-right";
|
|
273
|
+
};
|
|
274
|
+
const dragConstraints = {
|
|
275
|
+
left: edgePadding,
|
|
276
|
+
right: dimensions.width - width - edgePadding,
|
|
277
|
+
top: edgePadding,
|
|
278
|
+
bottom: dimensions.height - height - edgePadding
|
|
279
|
+
};
|
|
280
|
+
const floatingStyle = {
|
|
281
|
+
position: "absolute",
|
|
282
|
+
width,
|
|
283
|
+
height,
|
|
284
|
+
borderRadius,
|
|
285
|
+
boxShadow,
|
|
286
|
+
overflow: "hidden",
|
|
287
|
+
cursor: "grab",
|
|
288
|
+
zIndex: 100,
|
|
289
|
+
touchAction: "none",
|
|
290
|
+
left: 0,
|
|
291
|
+
top: 0,
|
|
292
|
+
...style
|
|
293
|
+
};
|
|
294
|
+
const handleDragEnd = () => {
|
|
295
|
+
const currentX = x.get();
|
|
296
|
+
const currentY = y.get();
|
|
297
|
+
const nearestCorner = findNearestCorner(currentX, currentY);
|
|
298
|
+
setCurrentAnchor(nearestCorner);
|
|
299
|
+
onAnchorChange?.(nearestCorner);
|
|
300
|
+
const snapPos = getCornerPosition(nearestCorner);
|
|
301
|
+
const springConfig = { type: "spring", stiffness: 400, damping: 30 };
|
|
302
|
+
react.animate(x, snapPos.x, springConfig);
|
|
303
|
+
react.animate(y, snapPos.y, springConfig);
|
|
304
|
+
};
|
|
305
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
306
|
+
react.motion.div,
|
|
307
|
+
{
|
|
308
|
+
ref,
|
|
309
|
+
drag: true,
|
|
310
|
+
dragMomentum: false,
|
|
311
|
+
dragElastic: 0.1,
|
|
312
|
+
dragConstraints,
|
|
313
|
+
style: { ...floatingStyle, x, y },
|
|
314
|
+
className,
|
|
315
|
+
onDragEnd: handleDragEnd,
|
|
316
|
+
whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
|
|
317
|
+
transition: transition ?? { type: "spring", stiffness: 400, damping: 30 },
|
|
318
|
+
...props
|
|
319
|
+
},
|
|
186
320
|
children
|
|
187
321
|
);
|
|
188
322
|
}
|
|
@@ -218,6 +352,7 @@ exports.getAspectRatio = meetLayoutGridCore.getAspectRatio;
|
|
|
218
352
|
exports.getGridItemDimensions = meetLayoutGridCore.getGridItemDimensions;
|
|
219
353
|
exports.getSpringConfig = meetLayoutGridCore.getSpringConfig;
|
|
220
354
|
exports.springPresets = meetLayoutGridCore.springPresets;
|
|
355
|
+
exports.FloatingGridItem = FloatingGridItem;
|
|
221
356
|
exports.GridContainer = GridContainer;
|
|
222
357
|
exports.GridContext = GridContext;
|
|
223
358
|
exports.GridItem = GridItem;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@thangdevalone/meet-layout-grid-core';
|
|
4
|
-
export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
3
|
+
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode, ItemAspectRatio, ContentDimensions } from '@thangdevalone/meet-layout-grid-core';
|
|
4
|
+
export { ContentDimensions, GridDimensions, GridOptions, ItemAspectRatio, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
5
5
|
import { HTMLMotionProps, Transition } from 'motion/react';
|
|
6
6
|
|
|
7
7
|
interface GridContextValue {
|
|
@@ -59,10 +59,8 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
|
|
|
59
59
|
count?: number;
|
|
60
60
|
/** Layout mode */
|
|
61
61
|
layoutMode?: LayoutMode;
|
|
62
|
-
/** Index of pinned item */
|
|
62
|
+
/** Index of pinned/focused item (main participant for spotlight/sidebar modes) */
|
|
63
63
|
pinnedIndex?: number;
|
|
64
|
-
/** Index of active speaker */
|
|
65
|
-
speakerIndex?: number;
|
|
66
64
|
/** Sidebar position */
|
|
67
65
|
sidebarPosition?: 'left' | 'right' | 'top' | 'bottom';
|
|
68
66
|
/** Sidebar ratio (0-1) */
|
|
@@ -77,21 +75,71 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
|
|
|
77
75
|
maxItemsPerPage?: number;
|
|
78
76
|
/** Current page index (0-based) for pagination */
|
|
79
77
|
currentPage?: number;
|
|
80
|
-
/** Maximum visible
|
|
81
|
-
|
|
82
|
-
/** Current page for
|
|
83
|
-
|
|
78
|
+
/** Maximum visible items (0 = show all). In gallery mode: limits all items. In sidebar: limits "others". */
|
|
79
|
+
maxVisible?: number;
|
|
80
|
+
/** Current page for visible items (0-based), used when maxVisible > 0 */
|
|
81
|
+
currentVisiblePage?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Per-item aspect ratio configurations.
|
|
84
|
+
* Use different ratios for mobile (9:16), desktop (16:9), or whiteboard (fill).
|
|
85
|
+
* @example ['16:9', '9:16', 'fill', undefined]
|
|
86
|
+
*/
|
|
87
|
+
itemAspectRatios?: (ItemAspectRatio | undefined)[];
|
|
88
|
+
/**
|
|
89
|
+
* Enable flexible cell sizing based on item aspect ratios.
|
|
90
|
+
* When true, portrait items (9:16) get narrower cells, landscape items (16:9) get wider cells.
|
|
91
|
+
* Items are packed into rows intelligently.
|
|
92
|
+
* @default false
|
|
93
|
+
*/
|
|
94
|
+
flexLayout?: boolean;
|
|
84
95
|
}
|
|
85
96
|
/**
|
|
86
97
|
* Container component for the meet grid.
|
|
87
98
|
* Provides grid context to child GridItem components.
|
|
88
99
|
*/
|
|
89
100
|
declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
90
|
-
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
|
|
101
|
+
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition' | 'children'> {
|
|
91
102
|
/** Index of this item in the grid */
|
|
92
103
|
index: number;
|
|
93
|
-
/**
|
|
94
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Children to render inside the item.
|
|
106
|
+
* Can be a ReactNode or a render function that receives contentDimensions and visibility info.
|
|
107
|
+
* @example
|
|
108
|
+
* // Simple usage
|
|
109
|
+
* <GridItem index={0}><Video /></GridItem>
|
|
110
|
+
*
|
|
111
|
+
* // With contentDimensions for flexible aspect ratios
|
|
112
|
+
* <GridItem index={0}>
|
|
113
|
+
* {({ contentDimensions }) => (
|
|
114
|
+
* <Video style={{
|
|
115
|
+
* width: contentDimensions.width,
|
|
116
|
+
* height: contentDimensions.height,
|
|
117
|
+
* marginTop: contentDimensions.offsetTop,
|
|
118
|
+
* marginLeft: contentDimensions.offsetLeft
|
|
119
|
+
* }} />
|
|
120
|
+
* )}
|
|
121
|
+
* </GridItem>
|
|
122
|
+
*
|
|
123
|
+
* // With hidden count for '+X more' indicator
|
|
124
|
+
* <GridItem index={index}>
|
|
125
|
+
* {({ isLastVisibleOther, hiddenCount }) => (
|
|
126
|
+
* <div>
|
|
127
|
+
* {isLastVisibleOther && hiddenCount > 0 && (
|
|
128
|
+
* <span className="more-indicator">+{hiddenCount}</span>
|
|
129
|
+
* )}
|
|
130
|
+
* </div>
|
|
131
|
+
* )}
|
|
132
|
+
* </GridItem>
|
|
133
|
+
*/
|
|
134
|
+
children: ReactNode | ((props: {
|
|
135
|
+
contentDimensions: ContentDimensions;
|
|
136
|
+
/** True if this is the last visible item in the "others" section */
|
|
137
|
+
isLastVisibleOther: boolean;
|
|
138
|
+
/** Number of hidden items (for '+X more' indicator) */
|
|
139
|
+
hiddenCount: number;
|
|
140
|
+
}) => ReactNode);
|
|
141
|
+
/** Optional item-specific aspect ratio (overrides itemAspectRatios from container) */
|
|
142
|
+
itemAspectRatio?: ItemAspectRatio;
|
|
95
143
|
/** Custom transition override */
|
|
96
144
|
transition?: Transition;
|
|
97
145
|
/** Whether to disable animations */
|
|
@@ -106,6 +154,43 @@ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initia
|
|
|
106
154
|
* Automatically positions itself based on index in the grid.
|
|
107
155
|
*/
|
|
108
156
|
declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
157
|
+
interface FloatingGridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'children'> {
|
|
158
|
+
/** Children to render inside the floating item */
|
|
159
|
+
children: ReactNode;
|
|
160
|
+
/** Width of the floating item */
|
|
161
|
+
width?: number;
|
|
162
|
+
/** Height of the floating item */
|
|
163
|
+
height?: number;
|
|
164
|
+
/** Initial position (x, y from container edges) */
|
|
165
|
+
initialPosition?: {
|
|
166
|
+
x: number;
|
|
167
|
+
y: number;
|
|
168
|
+
};
|
|
169
|
+
/** Which corner to anchor: 'top-left', 'top-right', 'bottom-left', 'bottom-right' */
|
|
170
|
+
anchor?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
171
|
+
/** Whether the item is visible */
|
|
172
|
+
visible?: boolean;
|
|
173
|
+
/** Padding from container edges */
|
|
174
|
+
edgePadding?: number;
|
|
175
|
+
/** Callback when anchor changes after snap */
|
|
176
|
+
onAnchorChange?: (anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => void;
|
|
177
|
+
/** Custom transition */
|
|
178
|
+
transition?: Transition;
|
|
179
|
+
/** Border radius */
|
|
180
|
+
borderRadius?: number;
|
|
181
|
+
/** Box shadow */
|
|
182
|
+
boxShadow?: string;
|
|
183
|
+
/** Additional class name */
|
|
184
|
+
className?: string;
|
|
185
|
+
/** Custom style */
|
|
186
|
+
style?: CSSProperties;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Floating Grid Item component that can be dragged around the screen.
|
|
190
|
+
* Snaps to the nearest corner when released (like iOS/Android PiP).
|
|
191
|
+
* Perfect for Picture-in-Picture style floating video in zoom mode.
|
|
192
|
+
*/
|
|
193
|
+
declare const FloatingGridItem: React__default.ForwardRefExoticComponent<Omit<FloatingGridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
109
194
|
interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
110
195
|
/** Whether to show the overlay */
|
|
111
196
|
visible?: boolean;
|
|
@@ -117,5 +202,5 @@ interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
117
202
|
*/
|
|
118
203
|
declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
119
204
|
|
|
120
|
-
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
121
|
-
export type { GridContainerProps, GridItemProps, GridOverlayProps };
|
|
205
|
+
export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
206
|
+
export type { FloatingGridItemProps, GridContainerProps, GridItemProps, GridOverlayProps };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@thangdevalone/meet-layout-grid-core';
|
|
4
|
-
export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
3
|
+
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode, ItemAspectRatio, ContentDimensions } from '@thangdevalone/meet-layout-grid-core';
|
|
4
|
+
export { ContentDimensions, GridDimensions, GridOptions, ItemAspectRatio, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
5
5
|
import { HTMLMotionProps, Transition } from 'motion/react';
|
|
6
6
|
|
|
7
7
|
interface GridContextValue {
|
|
@@ -59,10 +59,8 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
|
|
|
59
59
|
count?: number;
|
|
60
60
|
/** Layout mode */
|
|
61
61
|
layoutMode?: LayoutMode;
|
|
62
|
-
/** Index of pinned item */
|
|
62
|
+
/** Index of pinned/focused item (main participant for spotlight/sidebar modes) */
|
|
63
63
|
pinnedIndex?: number;
|
|
64
|
-
/** Index of active speaker */
|
|
65
|
-
speakerIndex?: number;
|
|
66
64
|
/** Sidebar position */
|
|
67
65
|
sidebarPosition?: 'left' | 'right' | 'top' | 'bottom';
|
|
68
66
|
/** Sidebar ratio (0-1) */
|
|
@@ -77,21 +75,71 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
|
|
|
77
75
|
maxItemsPerPage?: number;
|
|
78
76
|
/** Current page index (0-based) for pagination */
|
|
79
77
|
currentPage?: number;
|
|
80
|
-
/** Maximum visible
|
|
81
|
-
|
|
82
|
-
/** Current page for
|
|
83
|
-
|
|
78
|
+
/** Maximum visible items (0 = show all). In gallery mode: limits all items. In sidebar: limits "others". */
|
|
79
|
+
maxVisible?: number;
|
|
80
|
+
/** Current page for visible items (0-based), used when maxVisible > 0 */
|
|
81
|
+
currentVisiblePage?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Per-item aspect ratio configurations.
|
|
84
|
+
* Use different ratios for mobile (9:16), desktop (16:9), or whiteboard (fill).
|
|
85
|
+
* @example ['16:9', '9:16', 'fill', undefined]
|
|
86
|
+
*/
|
|
87
|
+
itemAspectRatios?: (ItemAspectRatio | undefined)[];
|
|
88
|
+
/**
|
|
89
|
+
* Enable flexible cell sizing based on item aspect ratios.
|
|
90
|
+
* When true, portrait items (9:16) get narrower cells, landscape items (16:9) get wider cells.
|
|
91
|
+
* Items are packed into rows intelligently.
|
|
92
|
+
* @default false
|
|
93
|
+
*/
|
|
94
|
+
flexLayout?: boolean;
|
|
84
95
|
}
|
|
85
96
|
/**
|
|
86
97
|
* Container component for the meet grid.
|
|
87
98
|
* Provides grid context to child GridItem components.
|
|
88
99
|
*/
|
|
89
100
|
declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
90
|
-
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
|
|
101
|
+
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition' | 'children'> {
|
|
91
102
|
/** Index of this item in the grid */
|
|
92
103
|
index: number;
|
|
93
|
-
/**
|
|
94
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Children to render inside the item.
|
|
106
|
+
* Can be a ReactNode or a render function that receives contentDimensions and visibility info.
|
|
107
|
+
* @example
|
|
108
|
+
* // Simple usage
|
|
109
|
+
* <GridItem index={0}><Video /></GridItem>
|
|
110
|
+
*
|
|
111
|
+
* // With contentDimensions for flexible aspect ratios
|
|
112
|
+
* <GridItem index={0}>
|
|
113
|
+
* {({ contentDimensions }) => (
|
|
114
|
+
* <Video style={{
|
|
115
|
+
* width: contentDimensions.width,
|
|
116
|
+
* height: contentDimensions.height,
|
|
117
|
+
* marginTop: contentDimensions.offsetTop,
|
|
118
|
+
* marginLeft: contentDimensions.offsetLeft
|
|
119
|
+
* }} />
|
|
120
|
+
* )}
|
|
121
|
+
* </GridItem>
|
|
122
|
+
*
|
|
123
|
+
* // With hidden count for '+X more' indicator
|
|
124
|
+
* <GridItem index={index}>
|
|
125
|
+
* {({ isLastVisibleOther, hiddenCount }) => (
|
|
126
|
+
* <div>
|
|
127
|
+
* {isLastVisibleOther && hiddenCount > 0 && (
|
|
128
|
+
* <span className="more-indicator">+{hiddenCount}</span>
|
|
129
|
+
* )}
|
|
130
|
+
* </div>
|
|
131
|
+
* )}
|
|
132
|
+
* </GridItem>
|
|
133
|
+
*/
|
|
134
|
+
children: ReactNode | ((props: {
|
|
135
|
+
contentDimensions: ContentDimensions;
|
|
136
|
+
/** True if this is the last visible item in the "others" section */
|
|
137
|
+
isLastVisibleOther: boolean;
|
|
138
|
+
/** Number of hidden items (for '+X more' indicator) */
|
|
139
|
+
hiddenCount: number;
|
|
140
|
+
}) => ReactNode);
|
|
141
|
+
/** Optional item-specific aspect ratio (overrides itemAspectRatios from container) */
|
|
142
|
+
itemAspectRatio?: ItemAspectRatio;
|
|
95
143
|
/** Custom transition override */
|
|
96
144
|
transition?: Transition;
|
|
97
145
|
/** Whether to disable animations */
|
|
@@ -106,6 +154,43 @@ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initia
|
|
|
106
154
|
* Automatically positions itself based on index in the grid.
|
|
107
155
|
*/
|
|
108
156
|
declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
157
|
+
interface FloatingGridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'children'> {
|
|
158
|
+
/** Children to render inside the floating item */
|
|
159
|
+
children: ReactNode;
|
|
160
|
+
/** Width of the floating item */
|
|
161
|
+
width?: number;
|
|
162
|
+
/** Height of the floating item */
|
|
163
|
+
height?: number;
|
|
164
|
+
/** Initial position (x, y from container edges) */
|
|
165
|
+
initialPosition?: {
|
|
166
|
+
x: number;
|
|
167
|
+
y: number;
|
|
168
|
+
};
|
|
169
|
+
/** Which corner to anchor: 'top-left', 'top-right', 'bottom-left', 'bottom-right' */
|
|
170
|
+
anchor?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
171
|
+
/** Whether the item is visible */
|
|
172
|
+
visible?: boolean;
|
|
173
|
+
/** Padding from container edges */
|
|
174
|
+
edgePadding?: number;
|
|
175
|
+
/** Callback when anchor changes after snap */
|
|
176
|
+
onAnchorChange?: (anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => void;
|
|
177
|
+
/** Custom transition */
|
|
178
|
+
transition?: Transition;
|
|
179
|
+
/** Border radius */
|
|
180
|
+
borderRadius?: number;
|
|
181
|
+
/** Box shadow */
|
|
182
|
+
boxShadow?: string;
|
|
183
|
+
/** Additional class name */
|
|
184
|
+
className?: string;
|
|
185
|
+
/** Custom style */
|
|
186
|
+
style?: CSSProperties;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Floating Grid Item component that can be dragged around the screen.
|
|
190
|
+
* Snaps to the nearest corner when released (like iOS/Android PiP).
|
|
191
|
+
* Perfect for Picture-in-Picture style floating video in zoom mode.
|
|
192
|
+
*/
|
|
193
|
+
declare const FloatingGridItem: React__default.ForwardRefExoticComponent<Omit<FloatingGridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
109
194
|
interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
110
195
|
/** Whether to show the overlay */
|
|
111
196
|
visible?: boolean;
|
|
@@ -117,5 +202,5 @@ interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
117
202
|
*/
|
|
118
203
|
declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
119
204
|
|
|
120
|
-
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
121
|
-
export type { GridContainerProps, GridItemProps, GridOverlayProps };
|
|
205
|
+
export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
206
|
+
export type { FloatingGridItemProps, GridContainerProps, GridItemProps, GridOverlayProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@thangdevalone/meet-layout-grid-core';
|
|
4
|
-
export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
3
|
+
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode, ItemAspectRatio, ContentDimensions } from '@thangdevalone/meet-layout-grid-core';
|
|
4
|
+
export { ContentDimensions, GridDimensions, GridOptions, ItemAspectRatio, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
5
5
|
import { HTMLMotionProps, Transition } from 'motion/react';
|
|
6
6
|
|
|
7
7
|
interface GridContextValue {
|
|
@@ -59,10 +59,8 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
|
|
|
59
59
|
count?: number;
|
|
60
60
|
/** Layout mode */
|
|
61
61
|
layoutMode?: LayoutMode;
|
|
62
|
-
/** Index of pinned item */
|
|
62
|
+
/** Index of pinned/focused item (main participant for spotlight/sidebar modes) */
|
|
63
63
|
pinnedIndex?: number;
|
|
64
|
-
/** Index of active speaker */
|
|
65
|
-
speakerIndex?: number;
|
|
66
64
|
/** Sidebar position */
|
|
67
65
|
sidebarPosition?: 'left' | 'right' | 'top' | 'bottom';
|
|
68
66
|
/** Sidebar ratio (0-1) */
|
|
@@ -77,21 +75,71 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
|
|
|
77
75
|
maxItemsPerPage?: number;
|
|
78
76
|
/** Current page index (0-based) for pagination */
|
|
79
77
|
currentPage?: number;
|
|
80
|
-
/** Maximum visible
|
|
81
|
-
|
|
82
|
-
/** Current page for
|
|
83
|
-
|
|
78
|
+
/** Maximum visible items (0 = show all). In gallery mode: limits all items. In sidebar: limits "others". */
|
|
79
|
+
maxVisible?: number;
|
|
80
|
+
/** Current page for visible items (0-based), used when maxVisible > 0 */
|
|
81
|
+
currentVisiblePage?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Per-item aspect ratio configurations.
|
|
84
|
+
* Use different ratios for mobile (9:16), desktop (16:9), or whiteboard (fill).
|
|
85
|
+
* @example ['16:9', '9:16', 'fill', undefined]
|
|
86
|
+
*/
|
|
87
|
+
itemAspectRatios?: (ItemAspectRatio | undefined)[];
|
|
88
|
+
/**
|
|
89
|
+
* Enable flexible cell sizing based on item aspect ratios.
|
|
90
|
+
* When true, portrait items (9:16) get narrower cells, landscape items (16:9) get wider cells.
|
|
91
|
+
* Items are packed into rows intelligently.
|
|
92
|
+
* @default false
|
|
93
|
+
*/
|
|
94
|
+
flexLayout?: boolean;
|
|
84
95
|
}
|
|
85
96
|
/**
|
|
86
97
|
* Container component for the meet grid.
|
|
87
98
|
* Provides grid context to child GridItem components.
|
|
88
99
|
*/
|
|
89
100
|
declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
90
|
-
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
|
|
101
|
+
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition' | 'children'> {
|
|
91
102
|
/** Index of this item in the grid */
|
|
92
103
|
index: number;
|
|
93
|
-
/**
|
|
94
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Children to render inside the item.
|
|
106
|
+
* Can be a ReactNode or a render function that receives contentDimensions and visibility info.
|
|
107
|
+
* @example
|
|
108
|
+
* // Simple usage
|
|
109
|
+
* <GridItem index={0}><Video /></GridItem>
|
|
110
|
+
*
|
|
111
|
+
* // With contentDimensions for flexible aspect ratios
|
|
112
|
+
* <GridItem index={0}>
|
|
113
|
+
* {({ contentDimensions }) => (
|
|
114
|
+
* <Video style={{
|
|
115
|
+
* width: contentDimensions.width,
|
|
116
|
+
* height: contentDimensions.height,
|
|
117
|
+
* marginTop: contentDimensions.offsetTop,
|
|
118
|
+
* marginLeft: contentDimensions.offsetLeft
|
|
119
|
+
* }} />
|
|
120
|
+
* )}
|
|
121
|
+
* </GridItem>
|
|
122
|
+
*
|
|
123
|
+
* // With hidden count for '+X more' indicator
|
|
124
|
+
* <GridItem index={index}>
|
|
125
|
+
* {({ isLastVisibleOther, hiddenCount }) => (
|
|
126
|
+
* <div>
|
|
127
|
+
* {isLastVisibleOther && hiddenCount > 0 && (
|
|
128
|
+
* <span className="more-indicator">+{hiddenCount}</span>
|
|
129
|
+
* )}
|
|
130
|
+
* </div>
|
|
131
|
+
* )}
|
|
132
|
+
* </GridItem>
|
|
133
|
+
*/
|
|
134
|
+
children: ReactNode | ((props: {
|
|
135
|
+
contentDimensions: ContentDimensions;
|
|
136
|
+
/** True if this is the last visible item in the "others" section */
|
|
137
|
+
isLastVisibleOther: boolean;
|
|
138
|
+
/** Number of hidden items (for '+X more' indicator) */
|
|
139
|
+
hiddenCount: number;
|
|
140
|
+
}) => ReactNode);
|
|
141
|
+
/** Optional item-specific aspect ratio (overrides itemAspectRatios from container) */
|
|
142
|
+
itemAspectRatio?: ItemAspectRatio;
|
|
95
143
|
/** Custom transition override */
|
|
96
144
|
transition?: Transition;
|
|
97
145
|
/** Whether to disable animations */
|
|
@@ -106,6 +154,43 @@ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initia
|
|
|
106
154
|
* Automatically positions itself based on index in the grid.
|
|
107
155
|
*/
|
|
108
156
|
declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
157
|
+
interface FloatingGridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'children'> {
|
|
158
|
+
/** Children to render inside the floating item */
|
|
159
|
+
children: ReactNode;
|
|
160
|
+
/** Width of the floating item */
|
|
161
|
+
width?: number;
|
|
162
|
+
/** Height of the floating item */
|
|
163
|
+
height?: number;
|
|
164
|
+
/** Initial position (x, y from container edges) */
|
|
165
|
+
initialPosition?: {
|
|
166
|
+
x: number;
|
|
167
|
+
y: number;
|
|
168
|
+
};
|
|
169
|
+
/** Which corner to anchor: 'top-left', 'top-right', 'bottom-left', 'bottom-right' */
|
|
170
|
+
anchor?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
171
|
+
/** Whether the item is visible */
|
|
172
|
+
visible?: boolean;
|
|
173
|
+
/** Padding from container edges */
|
|
174
|
+
edgePadding?: number;
|
|
175
|
+
/** Callback when anchor changes after snap */
|
|
176
|
+
onAnchorChange?: (anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => void;
|
|
177
|
+
/** Custom transition */
|
|
178
|
+
transition?: Transition;
|
|
179
|
+
/** Border radius */
|
|
180
|
+
borderRadius?: number;
|
|
181
|
+
/** Box shadow */
|
|
182
|
+
boxShadow?: string;
|
|
183
|
+
/** Additional class name */
|
|
184
|
+
className?: string;
|
|
185
|
+
/** Custom style */
|
|
186
|
+
style?: CSSProperties;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Floating Grid Item component that can be dragged around the screen.
|
|
190
|
+
* Snaps to the nearest corner when released (like iOS/Android PiP).
|
|
191
|
+
* Perfect for Picture-in-Picture style floating video in zoom mode.
|
|
192
|
+
*/
|
|
193
|
+
declare const FloatingGridItem: React__default.ForwardRefExoticComponent<Omit<FloatingGridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
109
194
|
interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
110
195
|
/** Whether to show the overlay */
|
|
111
196
|
visible?: boolean;
|
|
@@ -117,5 +202,5 @@ interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
117
202
|
*/
|
|
118
203
|
declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
119
204
|
|
|
120
|
-
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
121
|
-
export type { GridContainerProps, GridItemProps, GridOverlayProps };
|
|
205
|
+
export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
206
|
+
export type { FloatingGridItemProps, GridContainerProps, GridItemProps, GridOverlayProps };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { createContext, useContext, useState, useEffect, useMemo, forwardRef, useRef } from 'react';
|
|
2
2
|
import { createMeetGrid, getSpringConfig } from '@thangdevalone/meet-layout-grid-core';
|
|
3
3
|
export { createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
4
|
-
import { motion } from 'motion/react';
|
|
4
|
+
import { motion, useMotionValue, animate } from 'motion/react';
|
|
5
5
|
|
|
6
6
|
const GridContext = createContext(null);
|
|
7
7
|
function useGridContext() {
|
|
@@ -41,6 +41,7 @@ function useGridDimensions(ref) {
|
|
|
41
41
|
return dimensions;
|
|
42
42
|
}
|
|
43
43
|
function useMeetGrid(options) {
|
|
44
|
+
const itemAspectRatiosKey = options.itemAspectRatios?.join(",") ?? "";
|
|
44
45
|
return useMemo(() => {
|
|
45
46
|
return createMeetGrid(options);
|
|
46
47
|
}, [
|
|
@@ -51,13 +52,14 @@ function useMeetGrid(options) {
|
|
|
51
52
|
options.gap,
|
|
52
53
|
options.layoutMode,
|
|
53
54
|
options.pinnedIndex,
|
|
54
|
-
options.speakerIndex,
|
|
55
55
|
options.sidebarPosition,
|
|
56
56
|
options.sidebarRatio,
|
|
57
57
|
options.maxItemsPerPage,
|
|
58
58
|
options.currentPage,
|
|
59
|
-
options.
|
|
60
|
-
options.
|
|
59
|
+
options.maxVisible,
|
|
60
|
+
options.currentVisiblePage,
|
|
61
|
+
options.flexLayout,
|
|
62
|
+
itemAspectRatiosKey
|
|
61
63
|
]);
|
|
62
64
|
}
|
|
63
65
|
function useGridAnimation(preset = "smooth") {
|
|
@@ -72,7 +74,6 @@ const GridContainer = forwardRef(
|
|
|
72
74
|
count,
|
|
73
75
|
layoutMode = "gallery",
|
|
74
76
|
pinnedIndex,
|
|
75
|
-
speakerIndex,
|
|
76
77
|
sidebarPosition,
|
|
77
78
|
sidebarRatio,
|
|
78
79
|
springPreset = "smooth",
|
|
@@ -80,8 +81,10 @@ const GridContainer = forwardRef(
|
|
|
80
81
|
className,
|
|
81
82
|
maxItemsPerPage,
|
|
82
83
|
currentPage,
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
maxVisible,
|
|
85
|
+
currentVisiblePage,
|
|
86
|
+
itemAspectRatios,
|
|
87
|
+
flexLayout,
|
|
85
88
|
...props
|
|
86
89
|
}, forwardedRef) {
|
|
87
90
|
const internalRef = useRef(null);
|
|
@@ -95,13 +98,14 @@ const GridContainer = forwardRef(
|
|
|
95
98
|
gap,
|
|
96
99
|
layoutMode,
|
|
97
100
|
pinnedIndex,
|
|
98
|
-
speakerIndex,
|
|
99
101
|
sidebarPosition,
|
|
100
102
|
sidebarRatio,
|
|
101
103
|
maxItemsPerPage,
|
|
102
104
|
currentPage,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
maxVisible,
|
|
106
|
+
currentVisiblePage,
|
|
107
|
+
itemAspectRatios,
|
|
108
|
+
flexLayout
|
|
105
109
|
};
|
|
106
110
|
const grid = useMeetGrid(gridOptions);
|
|
107
111
|
const containerStyle = {
|
|
@@ -118,6 +122,7 @@ const GridItem = forwardRef(
|
|
|
118
122
|
function GridItem2({
|
|
119
123
|
index,
|
|
120
124
|
children,
|
|
125
|
+
itemAspectRatio,
|
|
121
126
|
transition: customTransition,
|
|
122
127
|
disableAnimation = false,
|
|
123
128
|
className,
|
|
@@ -133,6 +138,7 @@ const GridItem = forwardRef(
|
|
|
133
138
|
}
|
|
134
139
|
const { top, left } = grid.getPosition(index);
|
|
135
140
|
const { width, height } = grid.getItemDimensions(index);
|
|
141
|
+
const contentDimensions = grid.getItemContentDimensions(index, itemAspectRatio);
|
|
136
142
|
const isMain = grid.isMainItem(index);
|
|
137
143
|
if (grid.layoutMode === "spotlight" && !isMain) {
|
|
138
144
|
return null;
|
|
@@ -150,6 +156,15 @@ const GridItem = forwardRef(
|
|
|
150
156
|
top,
|
|
151
157
|
left
|
|
152
158
|
};
|
|
159
|
+
const lastVisibleOthersIndex = grid.getLastVisibleOthersIndex();
|
|
160
|
+
const isLastVisibleOther = index === lastVisibleOthersIndex;
|
|
161
|
+
const hiddenCount = grid.hiddenCount;
|
|
162
|
+
const renderChildren = () => {
|
|
163
|
+
if (typeof children === "function") {
|
|
164
|
+
return children({ contentDimensions, isLastVisibleOther, hiddenCount });
|
|
165
|
+
}
|
|
166
|
+
return children;
|
|
167
|
+
};
|
|
153
168
|
if (disableAnimation) {
|
|
154
169
|
return /* @__PURE__ */ React.createElement(
|
|
155
170
|
"div",
|
|
@@ -161,7 +176,7 @@ const GridItem = forwardRef(
|
|
|
161
176
|
"data-grid-main": isMain,
|
|
162
177
|
...props
|
|
163
178
|
},
|
|
164
|
-
|
|
179
|
+
renderChildren()
|
|
165
180
|
);
|
|
166
181
|
}
|
|
167
182
|
return /* @__PURE__ */ React.createElement(
|
|
@@ -178,6 +193,125 @@ const GridItem = forwardRef(
|
|
|
178
193
|
"data-grid-main": isMain,
|
|
179
194
|
...props
|
|
180
195
|
},
|
|
196
|
+
renderChildren()
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
const FloatingGridItem = forwardRef(
|
|
201
|
+
function FloatingGridItem2({
|
|
202
|
+
children,
|
|
203
|
+
width = 120,
|
|
204
|
+
height = 160,
|
|
205
|
+
initialPosition = { x: 16, y: 16 },
|
|
206
|
+
anchor: initialAnchor = "bottom-right",
|
|
207
|
+
visible = true,
|
|
208
|
+
edgePadding = 12,
|
|
209
|
+
onAnchorChange,
|
|
210
|
+
transition,
|
|
211
|
+
borderRadius = 12,
|
|
212
|
+
boxShadow = "0 4px 20px rgba(0,0,0,0.3)",
|
|
213
|
+
className,
|
|
214
|
+
style,
|
|
215
|
+
...props
|
|
216
|
+
}, ref) {
|
|
217
|
+
const { dimensions } = useGridContext();
|
|
218
|
+
const [currentAnchor, setCurrentAnchor] = React.useState(initialAnchor);
|
|
219
|
+
const x = useMotionValue(0);
|
|
220
|
+
const y = useMotionValue(0);
|
|
221
|
+
const [isInitialized, setIsInitialized] = React.useState(false);
|
|
222
|
+
const getCornerPosition = React.useCallback((corner) => {
|
|
223
|
+
const padding = edgePadding + initialPosition.x;
|
|
224
|
+
switch (corner) {
|
|
225
|
+
case "top-left":
|
|
226
|
+
return { x: padding, y: padding };
|
|
227
|
+
case "top-right":
|
|
228
|
+
return { x: dimensions.width - width - padding, y: padding };
|
|
229
|
+
case "bottom-left":
|
|
230
|
+
return { x: padding, y: dimensions.height - height - padding };
|
|
231
|
+
case "bottom-right":
|
|
232
|
+
default:
|
|
233
|
+
return { x: dimensions.width - width - padding, y: dimensions.height - height - padding };
|
|
234
|
+
}
|
|
235
|
+
}, [dimensions.width, dimensions.height, width, height, edgePadding, initialPosition.x]);
|
|
236
|
+
React.useEffect(() => {
|
|
237
|
+
if (dimensions.width > 0 && dimensions.height > 0 && !isInitialized) {
|
|
238
|
+
const pos = getCornerPosition(currentAnchor);
|
|
239
|
+
x.set(pos.x);
|
|
240
|
+
y.set(pos.y);
|
|
241
|
+
setIsInitialized(true);
|
|
242
|
+
}
|
|
243
|
+
}, [dimensions.width, dimensions.height, currentAnchor, getCornerPosition, isInitialized, x, y]);
|
|
244
|
+
React.useEffect(() => {
|
|
245
|
+
if (isInitialized && dimensions.width > 0 && dimensions.height > 0) {
|
|
246
|
+
const pos = getCornerPosition(currentAnchor);
|
|
247
|
+
const springConfig = { type: "spring", stiffness: 400, damping: 30 };
|
|
248
|
+
animate(x, pos.x, springConfig);
|
|
249
|
+
animate(y, pos.y, springConfig);
|
|
250
|
+
}
|
|
251
|
+
}, [currentAnchor, dimensions.width, dimensions.height, getCornerPosition, isInitialized, x, y]);
|
|
252
|
+
if (!visible || dimensions.width === 0 || dimensions.height === 0)
|
|
253
|
+
return null;
|
|
254
|
+
const findNearestCorner = (posX, posY) => {
|
|
255
|
+
const centerX = posX + width / 2;
|
|
256
|
+
const centerY = posY + height / 2;
|
|
257
|
+
const containerCenterX = dimensions.width / 2;
|
|
258
|
+
const containerCenterY = dimensions.height / 2;
|
|
259
|
+
const isLeft = centerX < containerCenterX;
|
|
260
|
+
const isTop = centerY < containerCenterY;
|
|
261
|
+
if (isTop && isLeft)
|
|
262
|
+
return "top-left";
|
|
263
|
+
if (isTop && !isLeft)
|
|
264
|
+
return "top-right";
|
|
265
|
+
if (!isTop && isLeft)
|
|
266
|
+
return "bottom-left";
|
|
267
|
+
return "bottom-right";
|
|
268
|
+
};
|
|
269
|
+
const dragConstraints = {
|
|
270
|
+
left: edgePadding,
|
|
271
|
+
right: dimensions.width - width - edgePadding,
|
|
272
|
+
top: edgePadding,
|
|
273
|
+
bottom: dimensions.height - height - edgePadding
|
|
274
|
+
};
|
|
275
|
+
const floatingStyle = {
|
|
276
|
+
position: "absolute",
|
|
277
|
+
width,
|
|
278
|
+
height,
|
|
279
|
+
borderRadius,
|
|
280
|
+
boxShadow,
|
|
281
|
+
overflow: "hidden",
|
|
282
|
+
cursor: "grab",
|
|
283
|
+
zIndex: 100,
|
|
284
|
+
touchAction: "none",
|
|
285
|
+
left: 0,
|
|
286
|
+
top: 0,
|
|
287
|
+
...style
|
|
288
|
+
};
|
|
289
|
+
const handleDragEnd = () => {
|
|
290
|
+
const currentX = x.get();
|
|
291
|
+
const currentY = y.get();
|
|
292
|
+
const nearestCorner = findNearestCorner(currentX, currentY);
|
|
293
|
+
setCurrentAnchor(nearestCorner);
|
|
294
|
+
onAnchorChange?.(nearestCorner);
|
|
295
|
+
const snapPos = getCornerPosition(nearestCorner);
|
|
296
|
+
const springConfig = { type: "spring", stiffness: 400, damping: 30 };
|
|
297
|
+
animate(x, snapPos.x, springConfig);
|
|
298
|
+
animate(y, snapPos.y, springConfig);
|
|
299
|
+
};
|
|
300
|
+
return /* @__PURE__ */ React.createElement(
|
|
301
|
+
motion.div,
|
|
302
|
+
{
|
|
303
|
+
ref,
|
|
304
|
+
drag: true,
|
|
305
|
+
dragMomentum: false,
|
|
306
|
+
dragElastic: 0.1,
|
|
307
|
+
dragConstraints,
|
|
308
|
+
style: { ...floatingStyle, x, y },
|
|
309
|
+
className,
|
|
310
|
+
onDragEnd: handleDragEnd,
|
|
311
|
+
whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
|
|
312
|
+
transition: transition ?? { type: "spring", stiffness: 400, damping: 30 },
|
|
313
|
+
...props
|
|
314
|
+
},
|
|
181
315
|
children
|
|
182
316
|
);
|
|
183
317
|
}
|
|
@@ -206,4 +340,4 @@ const GridOverlay = forwardRef(
|
|
|
206
340
|
}
|
|
207
341
|
);
|
|
208
342
|
|
|
209
|
-
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
343
|
+
export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thangdevalone/meet-layout-grid-react",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "React integration for meet-layout-grid with Motion animations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"motion": "^11.15.0",
|
|
45
|
-
"@thangdevalone/meet-layout-grid-core": "1.0
|
|
45
|
+
"@thangdevalone/meet-layout-grid-core": "1.1.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^18.2.0",
|