@thangdevalone/meet-layout-grid-react 1.0.9 → 1.1.1
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 +50 -65
- 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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @thangdevalone/meet-layout-grid-react
|
|
2
2
|
|
|
3
|
-
React bindings for meet-layout-grid with Motion animations.
|
|
3
|
+
React 18+ bindings for meet-layout-grid with Motion animations.
|
|
4
|
+
|
|
5
|
+
> For full documentation, layout modes, and API reference, see the [main README](https://github.com/thangdevalone/meet-layout-grid#readme).
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -8,19 +10,14 @@ React bindings for meet-layout-grid with Motion animations.
|
|
|
8
10
|
npm install @thangdevalone/meet-layout-grid-react
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
## Quick
|
|
13
|
+
## Quick Start
|
|
12
14
|
|
|
13
15
|
```tsx
|
|
14
16
|
import { GridContainer, GridItem } from '@thangdevalone/meet-layout-grid-react'
|
|
15
17
|
|
|
16
18
|
function MeetingGrid({ participants }) {
|
|
17
19
|
return (
|
|
18
|
-
<GridContainer
|
|
19
|
-
aspectRatio="16:9"
|
|
20
|
-
gap={8}
|
|
21
|
-
layoutMode="gallery"
|
|
22
|
-
count={participants.length}
|
|
23
|
-
>
|
|
20
|
+
<GridContainer aspectRatio="16:9" gap={8} layoutMode="gallery" count={participants.length}>
|
|
24
21
|
{participants.map((p, index) => (
|
|
25
22
|
<GridItem key={p.id} index={index}>
|
|
26
23
|
<VideoTile participant={p} />
|
|
@@ -35,76 +32,64 @@ function MeetingGrid({ participants }) {
|
|
|
35
32
|
|
|
36
33
|
### `<GridContainer>`
|
|
37
34
|
|
|
38
|
-
Wraps the grid, computes layout, provides context.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</GridContainer>
|
|
54
|
-
```
|
|
35
|
+
Wraps the grid, computes layout, and provides context to children.
|
|
36
|
+
|
|
37
|
+
| Prop | Type | Default | Description |
|
|
38
|
+
| ------------------ | -------------- | ----------- | ------------------------------ |
|
|
39
|
+
| `aspectRatio` | `string` | `'16:9'` | Default tile aspect ratio |
|
|
40
|
+
| `gap` | `number` | `8` | Gap between tiles (px) |
|
|
41
|
+
| `count` | `number` | required | Number of items |
|
|
42
|
+
| `layoutMode` | `LayoutMode` | `'gallery'` | `gallery` \| `spotlight` |
|
|
43
|
+
| `pinnedIndex` | `number` | - | Pinned participant index |
|
|
44
|
+
| `othersPosition` | `string` | `'right'` | Thumbnail position in pin mode |
|
|
45
|
+
| `springPreset` | `SpringPreset` | `'smooth'` | Animation preset |
|
|
46
|
+
| `maxItemsPerPage` | `number` | - | Max items per page |
|
|
47
|
+
| `currentPage` | `number` | `0` | Current page |
|
|
48
|
+
| `maxVisible` | `number` | - | Max visible in others area |
|
|
49
|
+
| `itemAspectRatios` | `array` | - | Per-item aspect ratios |
|
|
55
50
|
|
|
56
51
|
### `<GridItem>`
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
```tsx
|
|
61
|
-
<GridItem index={0} disableAnimation={false}>
|
|
62
|
-
{content}
|
|
63
|
-
</GridItem>
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Hooks
|
|
53
|
+
Single grid cell with Motion layout animation.
|
|
67
54
|
|
|
68
|
-
|
|
55
|
+
| Prop | Type | Default | Description |
|
|
56
|
+
| ------------------ | --------- | -------- | ------------------------ |
|
|
57
|
+
| `index` | `number` | required | Item index |
|
|
58
|
+
| `disableAnimation` | `boolean` | `false` | Disable layout animation |
|
|
69
59
|
|
|
70
|
-
|
|
60
|
+
Render prop: `{({ contentDimensions, isLastVisibleOther, hiddenCount }) => ...}`
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
74
|
-
const dimensions = useGridDimensions(ref)
|
|
75
|
-
// { width: number, height: number }
|
|
76
|
-
```
|
|
62
|
+
### `<FloatingGridItem>`
|
|
77
63
|
|
|
78
|
-
|
|
64
|
+
Draggable Picture-in-Picture overlay with corner snapping.
|
|
79
65
|
|
|
80
|
-
|
|
66
|
+
| Prop | Type | Default | Description |
|
|
67
|
+
| ---------------- | ---------- | ---------------- | ---------------------------- |
|
|
68
|
+
| `width` | `number` | - | Floating item width |
|
|
69
|
+
| `height` | `number` | - | Floating item height |
|
|
70
|
+
| `anchor` | `string` | `'bottom-right'` | Corner anchor position |
|
|
71
|
+
| `visible` | `boolean` | `true` | Show/hide the floating item |
|
|
72
|
+
| `edgePadding` | `number` | - | Padding from container edges |
|
|
73
|
+
| `borderRadius` | `number` | - | Border radius |
|
|
74
|
+
| `onAnchorChange` | `function` | - | Callback when anchor changes |
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
const grid = useMeetGrid({
|
|
84
|
-
dimensions: { width: 800, height: 600 },
|
|
85
|
-
count: 6,
|
|
86
|
-
aspectRatio: '16:9',
|
|
87
|
-
gap: 8,
|
|
88
|
-
layoutMode: 'speaker',
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const { top, left } = grid.getPosition(index)
|
|
92
|
-
const { width, height } = grid.getItemDimensions(index)
|
|
93
|
-
```
|
|
76
|
+
### `<GridOverlay>`
|
|
94
77
|
|
|
95
|
-
|
|
78
|
+
Full-grid overlay for screen sharing, whiteboard, etc.
|
|
96
79
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
80
|
+
| Prop | Type | Default | Description |
|
|
81
|
+
| ----------------- | --------- | ------- | ------------------------ |
|
|
82
|
+
| `visible` | `boolean` | `false` | Show/hide the overlay |
|
|
83
|
+
| `backgroundColor` | `string` | - | Overlay background color |
|
|
101
84
|
|
|
102
|
-
##
|
|
85
|
+
## Hooks
|
|
103
86
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
| Hook | Description |
|
|
88
|
+
| ------------------------ | ----------------------------------------- |
|
|
89
|
+
| `useGridDimensions(ref)` | Track element size via ResizeObserver |
|
|
90
|
+
| `useMeetGrid(options)` | Compute grid layout programmatically |
|
|
91
|
+
| `useGridContext()` | Access grid context from child components |
|
|
92
|
+
| `useGridAnimation()` | Access animation config from context |
|
|
108
93
|
|
|
109
94
|
## License
|
|
110
95
|
|
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.
|
|
3
|
+
"version": "1.1.1",
|
|
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.
|
|
45
|
+
"@thangdevalone/meet-layout-grid-core": "1.1.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^18.2.0",
|