@thangdevalone/meet-layout-grid-react 1.0.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 +114 -0
- package/dist/index.cjs +213 -0
- package/dist/index.d.cts +113 -0
- package/dist/index.d.mts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.mjs +194 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @meet-layout-grid/react
|
|
2
|
+
|
|
3
|
+
React integration for meet-layout-grid with Motion animations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @meet-layout-grid/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { GridContainer, GridItem } from '@meet-layout-grid/react'
|
|
15
|
+
|
|
16
|
+
function MeetingGrid({ participants }) {
|
|
17
|
+
return (
|
|
18
|
+
<GridContainer
|
|
19
|
+
aspectRatio="16:9"
|
|
20
|
+
gap={8}
|
|
21
|
+
layoutMode="gallery"
|
|
22
|
+
count={participants.length}
|
|
23
|
+
>
|
|
24
|
+
{participants.map((p, index) => (
|
|
25
|
+
<GridItem key={p.id} index={index}>
|
|
26
|
+
<VideoTile participant={p} />
|
|
27
|
+
</GridItem>
|
|
28
|
+
))}
|
|
29
|
+
</GridContainer>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Components
|
|
35
|
+
|
|
36
|
+
### `<GridContainer>`
|
|
37
|
+
|
|
38
|
+
Container component that calculates grid layout and provides context.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<GridContainer
|
|
42
|
+
aspectRatio="16:9" // Item aspect ratio
|
|
43
|
+
gap={8} // Gap between items (px)
|
|
44
|
+
count={6} // Number of items
|
|
45
|
+
layoutMode="gallery" // Layout mode
|
|
46
|
+
speakerIndex={0} // Active speaker
|
|
47
|
+
pinnedIndex={0} // Pinned item
|
|
48
|
+
sidebarPosition="right" // Sidebar position
|
|
49
|
+
sidebarRatio={0.25} // Sidebar width ratio
|
|
50
|
+
springPreset="smooth" // Animation preset
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</GridContainer>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `<GridItem>`
|
|
57
|
+
|
|
58
|
+
Animated grid item with Motion spring animations.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<GridItem
|
|
62
|
+
index={0} // Item index (required)
|
|
63
|
+
disableAnimation={false} // Disable animations
|
|
64
|
+
>
|
|
65
|
+
{content}
|
|
66
|
+
</GridItem>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Hooks
|
|
70
|
+
|
|
71
|
+
### `useGridDimensions(ref)`
|
|
72
|
+
|
|
73
|
+
Track element dimensions with ResizeObserver.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
77
|
+
const dimensions = useGridDimensions(ref)
|
|
78
|
+
// { width: number, height: number }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `useMeetGrid(options)`
|
|
82
|
+
|
|
83
|
+
Calculate grid layout manually.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const grid = useMeetGrid({
|
|
87
|
+
dimensions: { width: 800, height: 600 },
|
|
88
|
+
count: 6,
|
|
89
|
+
aspectRatio: '16:9',
|
|
90
|
+
gap: 8,
|
|
91
|
+
layoutMode: 'speaker',
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const { top, left } = grid.getPosition(index)
|
|
95
|
+
const { width, height } = grid.getItemDimensions(index)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Layout Modes
|
|
99
|
+
|
|
100
|
+
- **gallery** - Equal-sized tiles
|
|
101
|
+
- **speaker** - Active speaker larger
|
|
102
|
+
- **spotlight** - Single participant focus
|
|
103
|
+
- **sidebar** - Main view + thumbnails
|
|
104
|
+
|
|
105
|
+
## Animation Presets
|
|
106
|
+
|
|
107
|
+
- `snappy` - Quick UI interactions
|
|
108
|
+
- `smooth` - Layout changes (default)
|
|
109
|
+
- `gentle` - Subtle effects
|
|
110
|
+
- `bouncy` - Playful effects
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const React = require('react');
|
|
4
|
+
const core = require('@meet-layout-grid/core');
|
|
5
|
+
const react = require('motion/react');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
8
|
+
|
|
9
|
+
const React__default = /*#__PURE__*/_interopDefaultCompat(React);
|
|
10
|
+
|
|
11
|
+
const GridContext = React.createContext(null);
|
|
12
|
+
function useGridContext() {
|
|
13
|
+
const context = React.useContext(GridContext);
|
|
14
|
+
if (!context) {
|
|
15
|
+
throw new Error("useGridContext must be used within a GridContainer");
|
|
16
|
+
}
|
|
17
|
+
return context;
|
|
18
|
+
}
|
|
19
|
+
function useGridDimensions(ref) {
|
|
20
|
+
const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
const element = ref.current;
|
|
23
|
+
if (!element) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const observer = new ResizeObserver((entries) => {
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const { clientWidth: width, clientHeight: height } = entry.target;
|
|
29
|
+
setDimensions((prev) => {
|
|
30
|
+
if (prev.width === width && prev.height === height) {
|
|
31
|
+
return prev;
|
|
32
|
+
}
|
|
33
|
+
return { width, height };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
observer.observe(element);
|
|
38
|
+
setDimensions({
|
|
39
|
+
width: element.clientWidth,
|
|
40
|
+
height: element.clientHeight
|
|
41
|
+
});
|
|
42
|
+
return () => {
|
|
43
|
+
observer.disconnect();
|
|
44
|
+
};
|
|
45
|
+
}, [ref]);
|
|
46
|
+
return dimensions;
|
|
47
|
+
}
|
|
48
|
+
function useMeetGrid(options) {
|
|
49
|
+
return React.useMemo(() => {
|
|
50
|
+
return core.createMeetGrid(options);
|
|
51
|
+
}, [
|
|
52
|
+
options.dimensions.width,
|
|
53
|
+
options.dimensions.height,
|
|
54
|
+
options.count,
|
|
55
|
+
options.aspectRatio,
|
|
56
|
+
options.gap,
|
|
57
|
+
options.layoutMode,
|
|
58
|
+
options.pinnedIndex,
|
|
59
|
+
options.speakerIndex,
|
|
60
|
+
options.sidebarPosition,
|
|
61
|
+
options.sidebarRatio
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
function useGridAnimation(preset = "smooth") {
|
|
65
|
+
return React.useMemo(() => core.getSpringConfig(preset), [preset]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const GridContainer = React.forwardRef(
|
|
69
|
+
function GridContainer2({
|
|
70
|
+
children,
|
|
71
|
+
aspectRatio = "16:9",
|
|
72
|
+
gap = 8,
|
|
73
|
+
count,
|
|
74
|
+
layoutMode = "gallery",
|
|
75
|
+
pinnedIndex,
|
|
76
|
+
speakerIndex,
|
|
77
|
+
sidebarPosition,
|
|
78
|
+
sidebarRatio,
|
|
79
|
+
springPreset = "smooth",
|
|
80
|
+
style,
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}, forwardedRef) {
|
|
84
|
+
const internalRef = React.useRef(null);
|
|
85
|
+
const ref = forwardedRef || internalRef;
|
|
86
|
+
const dimensions = useGridDimensions(ref);
|
|
87
|
+
const childCount = count ?? React__default.Children.count(children);
|
|
88
|
+
const gridOptions = {
|
|
89
|
+
dimensions,
|
|
90
|
+
count: childCount,
|
|
91
|
+
aspectRatio,
|
|
92
|
+
gap,
|
|
93
|
+
layoutMode,
|
|
94
|
+
pinnedIndex,
|
|
95
|
+
speakerIndex,
|
|
96
|
+
sidebarPosition,
|
|
97
|
+
sidebarRatio
|
|
98
|
+
};
|
|
99
|
+
const grid = useMeetGrid(gridOptions);
|
|
100
|
+
const containerStyle = {
|
|
101
|
+
position: "relative",
|
|
102
|
+
width: "100%",
|
|
103
|
+
height: "100%",
|
|
104
|
+
overflow: "hidden",
|
|
105
|
+
...style
|
|
106
|
+
};
|
|
107
|
+
return /* @__PURE__ */ React__default.createElement(GridContext.Provider, { value: { dimensions, grid, springPreset } }, /* @__PURE__ */ React__default.createElement("div", { ref, style: containerStyle, className, ...props }, children));
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
const GridItem = React.forwardRef(
|
|
111
|
+
function GridItem2({
|
|
112
|
+
index,
|
|
113
|
+
children,
|
|
114
|
+
transition: customTransition,
|
|
115
|
+
disableAnimation = false,
|
|
116
|
+
className,
|
|
117
|
+
style,
|
|
118
|
+
...props
|
|
119
|
+
}, ref) {
|
|
120
|
+
const { grid, springPreset } = useGridContext();
|
|
121
|
+
if (!grid) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const { top, left } = grid.getPosition(index);
|
|
125
|
+
const { width, height } = grid.getItemDimensions(index);
|
|
126
|
+
const isMain = grid.isMainItem(index);
|
|
127
|
+
if (grid.layoutMode === "spotlight" && !isMain) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const springConfig = core.getSpringConfig(springPreset);
|
|
131
|
+
const transition = customTransition ?? {
|
|
132
|
+
type: springConfig.type,
|
|
133
|
+
stiffness: springConfig.stiffness,
|
|
134
|
+
damping: springConfig.damping
|
|
135
|
+
};
|
|
136
|
+
const animatedStyle = {
|
|
137
|
+
position: "absolute",
|
|
138
|
+
width,
|
|
139
|
+
height,
|
|
140
|
+
top,
|
|
141
|
+
left
|
|
142
|
+
};
|
|
143
|
+
if (disableAnimation) {
|
|
144
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
145
|
+
"div",
|
|
146
|
+
{
|
|
147
|
+
ref,
|
|
148
|
+
style: { ...animatedStyle, ...style },
|
|
149
|
+
className,
|
|
150
|
+
"data-grid-index": index,
|
|
151
|
+
"data-grid-main": isMain,
|
|
152
|
+
...props
|
|
153
|
+
},
|
|
154
|
+
children
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
158
|
+
react.motion.div,
|
|
159
|
+
{
|
|
160
|
+
ref,
|
|
161
|
+
layout: true,
|
|
162
|
+
initial: false,
|
|
163
|
+
animate: animatedStyle,
|
|
164
|
+
transition,
|
|
165
|
+
style,
|
|
166
|
+
className,
|
|
167
|
+
"data-grid-index": index,
|
|
168
|
+
"data-grid-main": isMain,
|
|
169
|
+
...props
|
|
170
|
+
},
|
|
171
|
+
children
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
const GridOverlay = React.forwardRef(
|
|
176
|
+
function GridOverlay2({ visible = true, backgroundColor = "rgba(0,0,0,0.5)", children, style, ...props }, ref) {
|
|
177
|
+
if (!visible)
|
|
178
|
+
return null;
|
|
179
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
180
|
+
"div",
|
|
181
|
+
{
|
|
182
|
+
ref,
|
|
183
|
+
style: {
|
|
184
|
+
position: "absolute",
|
|
185
|
+
inset: 0,
|
|
186
|
+
display: "flex",
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
justifyContent: "center",
|
|
189
|
+
backgroundColor,
|
|
190
|
+
...style
|
|
191
|
+
},
|
|
192
|
+
...props
|
|
193
|
+
},
|
|
194
|
+
children
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
exports.createGrid = core.createGrid;
|
|
200
|
+
exports.createGridItemPositioner = core.createGridItemPositioner;
|
|
201
|
+
exports.createMeetGrid = core.createMeetGrid;
|
|
202
|
+
exports.getAspectRatio = core.getAspectRatio;
|
|
203
|
+
exports.getGridItemDimensions = core.getGridItemDimensions;
|
|
204
|
+
exports.getSpringConfig = core.getSpringConfig;
|
|
205
|
+
exports.springPresets = core.springPresets;
|
|
206
|
+
exports.GridContainer = GridContainer;
|
|
207
|
+
exports.GridContext = GridContext;
|
|
208
|
+
exports.GridItem = GridItem;
|
|
209
|
+
exports.GridOverlay = GridOverlay;
|
|
210
|
+
exports.useGridAnimation = useGridAnimation;
|
|
211
|
+
exports.useGridContext = useGridContext;
|
|
212
|
+
exports.useGridDimensions = useGridDimensions;
|
|
213
|
+
exports.useMeetGrid = useMeetGrid;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@meet-layout-grid/core';
|
|
4
|
+
export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@meet-layout-grid/core';
|
|
5
|
+
import { HTMLMotionProps, Transition } from 'motion/react';
|
|
6
|
+
|
|
7
|
+
interface GridContextValue {
|
|
8
|
+
dimensions: GridDimensions;
|
|
9
|
+
grid: MeetGridResult | null;
|
|
10
|
+
springPreset: SpringPreset;
|
|
11
|
+
}
|
|
12
|
+
declare const GridContext: React.Context<GridContextValue | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Hook to access the grid context
|
|
15
|
+
*/
|
|
16
|
+
declare function useGridContext(): GridContextValue;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A React hook to calculate dimensions of an element using ResizeObserver.
|
|
20
|
+
* @param ref An element ref
|
|
21
|
+
* @returns Dimensions of the element
|
|
22
|
+
*/
|
|
23
|
+
declare function useGridDimensions(ref: RefObject<HTMLElement | null>): GridDimensions;
|
|
24
|
+
/**
|
|
25
|
+
* React hook for creating a meet-style responsive grid.
|
|
26
|
+
* @param options Grid options including dimensions, count, aspectRatio, gap, and layoutMode
|
|
27
|
+
* @returns Grid result with width, height, and position getter
|
|
28
|
+
*/
|
|
29
|
+
declare function useMeetGrid(options: MeetGridOptions): MeetGridResult;
|
|
30
|
+
/**
|
|
31
|
+
* Hook to get animation configuration for Motion
|
|
32
|
+
*/
|
|
33
|
+
declare function useGridAnimation(preset?: SpringPreset): {
|
|
34
|
+
stiffness: 400;
|
|
35
|
+
damping: 30;
|
|
36
|
+
type: "spring";
|
|
37
|
+
} | {
|
|
38
|
+
stiffness: 300;
|
|
39
|
+
damping: 30;
|
|
40
|
+
type: "spring";
|
|
41
|
+
} | {
|
|
42
|
+
stiffness: 200;
|
|
43
|
+
damping: 25;
|
|
44
|
+
type: "spring";
|
|
45
|
+
} | {
|
|
46
|
+
stiffness: 400;
|
|
47
|
+
damping: 15;
|
|
48
|
+
type: "spring";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
52
|
+
/** Children to render inside the grid */
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
/** Aspect ratio in format "width:height" (e.g., "16:9") */
|
|
55
|
+
aspectRatio?: string;
|
|
56
|
+
/** Gap between items in pixels */
|
|
57
|
+
gap?: number;
|
|
58
|
+
/** Number of items (if not using GridItem children) */
|
|
59
|
+
count?: number;
|
|
60
|
+
/** Layout mode */
|
|
61
|
+
layoutMode?: LayoutMode;
|
|
62
|
+
/** Index of pinned item */
|
|
63
|
+
pinnedIndex?: number;
|
|
64
|
+
/** Index of active speaker */
|
|
65
|
+
speakerIndex?: number;
|
|
66
|
+
/** Sidebar position */
|
|
67
|
+
sidebarPosition?: 'left' | 'right' | 'bottom';
|
|
68
|
+
/** Sidebar ratio (0-1) */
|
|
69
|
+
sidebarRatio?: number;
|
|
70
|
+
/** Spring animation preset */
|
|
71
|
+
springPreset?: SpringPreset;
|
|
72
|
+
/** Custom container style */
|
|
73
|
+
style?: CSSProperties;
|
|
74
|
+
/** Additional class name */
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Container component for the meet grid.
|
|
79
|
+
* Provides grid context to child GridItem components.
|
|
80
|
+
*/
|
|
81
|
+
declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
82
|
+
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
|
|
83
|
+
/** Index of this item in the grid */
|
|
84
|
+
index: number;
|
|
85
|
+
/** Children to render inside the item */
|
|
86
|
+
children: ReactNode;
|
|
87
|
+
/** Custom transition override */
|
|
88
|
+
transition?: Transition;
|
|
89
|
+
/** Whether to disable animations */
|
|
90
|
+
disableAnimation?: boolean;
|
|
91
|
+
/** Additional class name */
|
|
92
|
+
className?: string;
|
|
93
|
+
/** Custom style (merged with animated styles) */
|
|
94
|
+
style?: CSSProperties;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Grid item component with Motion animations.
|
|
98
|
+
* Automatically positions itself based on index in the grid.
|
|
99
|
+
*/
|
|
100
|
+
declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
101
|
+
interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
102
|
+
/** Whether to show the overlay */
|
|
103
|
+
visible?: boolean;
|
|
104
|
+
/** Overlay background color */
|
|
105
|
+
backgroundColor?: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Overlay component for grid items (e.g., for muted indicator, name label)
|
|
109
|
+
*/
|
|
110
|
+
declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
111
|
+
|
|
112
|
+
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
113
|
+
export type { GridContainerProps, GridItemProps, GridOverlayProps };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@meet-layout-grid/core';
|
|
4
|
+
export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@meet-layout-grid/core';
|
|
5
|
+
import { HTMLMotionProps, Transition } from 'motion/react';
|
|
6
|
+
|
|
7
|
+
interface GridContextValue {
|
|
8
|
+
dimensions: GridDimensions;
|
|
9
|
+
grid: MeetGridResult | null;
|
|
10
|
+
springPreset: SpringPreset;
|
|
11
|
+
}
|
|
12
|
+
declare const GridContext: React.Context<GridContextValue | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Hook to access the grid context
|
|
15
|
+
*/
|
|
16
|
+
declare function useGridContext(): GridContextValue;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A React hook to calculate dimensions of an element using ResizeObserver.
|
|
20
|
+
* @param ref An element ref
|
|
21
|
+
* @returns Dimensions of the element
|
|
22
|
+
*/
|
|
23
|
+
declare function useGridDimensions(ref: RefObject<HTMLElement | null>): GridDimensions;
|
|
24
|
+
/**
|
|
25
|
+
* React hook for creating a meet-style responsive grid.
|
|
26
|
+
* @param options Grid options including dimensions, count, aspectRatio, gap, and layoutMode
|
|
27
|
+
* @returns Grid result with width, height, and position getter
|
|
28
|
+
*/
|
|
29
|
+
declare function useMeetGrid(options: MeetGridOptions): MeetGridResult;
|
|
30
|
+
/**
|
|
31
|
+
* Hook to get animation configuration for Motion
|
|
32
|
+
*/
|
|
33
|
+
declare function useGridAnimation(preset?: SpringPreset): {
|
|
34
|
+
stiffness: 400;
|
|
35
|
+
damping: 30;
|
|
36
|
+
type: "spring";
|
|
37
|
+
} | {
|
|
38
|
+
stiffness: 300;
|
|
39
|
+
damping: 30;
|
|
40
|
+
type: "spring";
|
|
41
|
+
} | {
|
|
42
|
+
stiffness: 200;
|
|
43
|
+
damping: 25;
|
|
44
|
+
type: "spring";
|
|
45
|
+
} | {
|
|
46
|
+
stiffness: 400;
|
|
47
|
+
damping: 15;
|
|
48
|
+
type: "spring";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
52
|
+
/** Children to render inside the grid */
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
/** Aspect ratio in format "width:height" (e.g., "16:9") */
|
|
55
|
+
aspectRatio?: string;
|
|
56
|
+
/** Gap between items in pixels */
|
|
57
|
+
gap?: number;
|
|
58
|
+
/** Number of items (if not using GridItem children) */
|
|
59
|
+
count?: number;
|
|
60
|
+
/** Layout mode */
|
|
61
|
+
layoutMode?: LayoutMode;
|
|
62
|
+
/** Index of pinned item */
|
|
63
|
+
pinnedIndex?: number;
|
|
64
|
+
/** Index of active speaker */
|
|
65
|
+
speakerIndex?: number;
|
|
66
|
+
/** Sidebar position */
|
|
67
|
+
sidebarPosition?: 'left' | 'right' | 'bottom';
|
|
68
|
+
/** Sidebar ratio (0-1) */
|
|
69
|
+
sidebarRatio?: number;
|
|
70
|
+
/** Spring animation preset */
|
|
71
|
+
springPreset?: SpringPreset;
|
|
72
|
+
/** Custom container style */
|
|
73
|
+
style?: CSSProperties;
|
|
74
|
+
/** Additional class name */
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Container component for the meet grid.
|
|
79
|
+
* Provides grid context to child GridItem components.
|
|
80
|
+
*/
|
|
81
|
+
declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
82
|
+
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
|
|
83
|
+
/** Index of this item in the grid */
|
|
84
|
+
index: number;
|
|
85
|
+
/** Children to render inside the item */
|
|
86
|
+
children: ReactNode;
|
|
87
|
+
/** Custom transition override */
|
|
88
|
+
transition?: Transition;
|
|
89
|
+
/** Whether to disable animations */
|
|
90
|
+
disableAnimation?: boolean;
|
|
91
|
+
/** Additional class name */
|
|
92
|
+
className?: string;
|
|
93
|
+
/** Custom style (merged with animated styles) */
|
|
94
|
+
style?: CSSProperties;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Grid item component with Motion animations.
|
|
98
|
+
* Automatically positions itself based on index in the grid.
|
|
99
|
+
*/
|
|
100
|
+
declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
101
|
+
interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
102
|
+
/** Whether to show the overlay */
|
|
103
|
+
visible?: boolean;
|
|
104
|
+
/** Overlay background color */
|
|
105
|
+
backgroundColor?: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Overlay component for grid items (e.g., for muted indicator, name label)
|
|
109
|
+
*/
|
|
110
|
+
declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
111
|
+
|
|
112
|
+
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
113
|
+
export type { GridContainerProps, GridItemProps, GridOverlayProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
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, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
|
|
5
|
+
import { HTMLMotionProps, Transition } from 'motion/react';
|
|
6
|
+
|
|
7
|
+
interface GridContextValue {
|
|
8
|
+
dimensions: GridDimensions;
|
|
9
|
+
grid: MeetGridResult | null;
|
|
10
|
+
springPreset: SpringPreset;
|
|
11
|
+
}
|
|
12
|
+
declare const GridContext: React.Context<GridContextValue | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Hook to access the grid context
|
|
15
|
+
*/
|
|
16
|
+
declare function useGridContext(): GridContextValue;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A React hook to calculate dimensions of an element using ResizeObserver.
|
|
20
|
+
* @param ref An element ref
|
|
21
|
+
* @returns Dimensions of the element
|
|
22
|
+
*/
|
|
23
|
+
declare function useGridDimensions(ref: RefObject<HTMLElement | null>): GridDimensions;
|
|
24
|
+
/**
|
|
25
|
+
* React hook for creating a meet-style responsive grid.
|
|
26
|
+
* @param options Grid options including dimensions, count, aspectRatio, gap, and layoutMode
|
|
27
|
+
* @returns Grid result with width, height, and position getter
|
|
28
|
+
*/
|
|
29
|
+
declare function useMeetGrid(options: MeetGridOptions): MeetGridResult;
|
|
30
|
+
/**
|
|
31
|
+
* Hook to get animation configuration for Motion
|
|
32
|
+
*/
|
|
33
|
+
declare function useGridAnimation(preset?: SpringPreset): {
|
|
34
|
+
stiffness: 400;
|
|
35
|
+
damping: 30;
|
|
36
|
+
type: "spring";
|
|
37
|
+
} | {
|
|
38
|
+
stiffness: 300;
|
|
39
|
+
damping: 30;
|
|
40
|
+
type: "spring";
|
|
41
|
+
} | {
|
|
42
|
+
stiffness: 200;
|
|
43
|
+
damping: 25;
|
|
44
|
+
type: "spring";
|
|
45
|
+
} | {
|
|
46
|
+
stiffness: 400;
|
|
47
|
+
damping: 15;
|
|
48
|
+
type: "spring";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
52
|
+
/** Children to render inside the grid */
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
/** Aspect ratio in format "width:height" (e.g., "16:9") */
|
|
55
|
+
aspectRatio?: string;
|
|
56
|
+
/** Gap between items in pixels */
|
|
57
|
+
gap?: number;
|
|
58
|
+
/** Number of items (if not using GridItem children) */
|
|
59
|
+
count?: number;
|
|
60
|
+
/** Layout mode */
|
|
61
|
+
layoutMode?: LayoutMode;
|
|
62
|
+
/** Index of pinned item */
|
|
63
|
+
pinnedIndex?: number;
|
|
64
|
+
/** Index of active speaker */
|
|
65
|
+
speakerIndex?: number;
|
|
66
|
+
/** Sidebar position */
|
|
67
|
+
sidebarPosition?: 'left' | 'right' | 'bottom';
|
|
68
|
+
/** Sidebar ratio (0-1) */
|
|
69
|
+
sidebarRatio?: number;
|
|
70
|
+
/** Spring animation preset */
|
|
71
|
+
springPreset?: SpringPreset;
|
|
72
|
+
/** Custom container style */
|
|
73
|
+
style?: CSSProperties;
|
|
74
|
+
/** Additional class name */
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Container component for the meet grid.
|
|
79
|
+
* Provides grid context to child GridItem components.
|
|
80
|
+
*/
|
|
81
|
+
declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
82
|
+
interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
|
|
83
|
+
/** Index of this item in the grid */
|
|
84
|
+
index: number;
|
|
85
|
+
/** Children to render inside the item */
|
|
86
|
+
children: ReactNode;
|
|
87
|
+
/** Custom transition override */
|
|
88
|
+
transition?: Transition;
|
|
89
|
+
/** Whether to disable animations */
|
|
90
|
+
disableAnimation?: boolean;
|
|
91
|
+
/** Additional class name */
|
|
92
|
+
className?: string;
|
|
93
|
+
/** Custom style (merged with animated styles) */
|
|
94
|
+
style?: CSSProperties;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Grid item component with Motion animations.
|
|
98
|
+
* Automatically positions itself based on index in the grid.
|
|
99
|
+
*/
|
|
100
|
+
declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
|
|
101
|
+
interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
|
|
102
|
+
/** Whether to show the overlay */
|
|
103
|
+
visible?: boolean;
|
|
104
|
+
/** Overlay background color */
|
|
105
|
+
backgroundColor?: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Overlay component for grid items (e.g., for muted indicator, name label)
|
|
109
|
+
*/
|
|
110
|
+
declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
|
|
111
|
+
|
|
112
|
+
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
|
113
|
+
export type { GridContainerProps, GridItemProps, GridOverlayProps };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useEffect, useMemo, forwardRef, useRef } from 'react';
|
|
2
|
+
import { createMeetGrid, getSpringConfig } from '@meet-layout-grid/core';
|
|
3
|
+
export { createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@meet-layout-grid/core';
|
|
4
|
+
import { motion } from 'motion/react';
|
|
5
|
+
|
|
6
|
+
const GridContext = createContext(null);
|
|
7
|
+
function useGridContext() {
|
|
8
|
+
const context = useContext(GridContext);
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error("useGridContext must be used within a GridContainer");
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
function useGridDimensions(ref) {
|
|
15
|
+
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const element = ref.current;
|
|
18
|
+
if (!element) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const observer = new ResizeObserver((entries) => {
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const { clientWidth: width, clientHeight: height } = entry.target;
|
|
24
|
+
setDimensions((prev) => {
|
|
25
|
+
if (prev.width === width && prev.height === height) {
|
|
26
|
+
return prev;
|
|
27
|
+
}
|
|
28
|
+
return { width, height };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
observer.observe(element);
|
|
33
|
+
setDimensions({
|
|
34
|
+
width: element.clientWidth,
|
|
35
|
+
height: element.clientHeight
|
|
36
|
+
});
|
|
37
|
+
return () => {
|
|
38
|
+
observer.disconnect();
|
|
39
|
+
};
|
|
40
|
+
}, [ref]);
|
|
41
|
+
return dimensions;
|
|
42
|
+
}
|
|
43
|
+
function useMeetGrid(options) {
|
|
44
|
+
return useMemo(() => {
|
|
45
|
+
return createMeetGrid(options);
|
|
46
|
+
}, [
|
|
47
|
+
options.dimensions.width,
|
|
48
|
+
options.dimensions.height,
|
|
49
|
+
options.count,
|
|
50
|
+
options.aspectRatio,
|
|
51
|
+
options.gap,
|
|
52
|
+
options.layoutMode,
|
|
53
|
+
options.pinnedIndex,
|
|
54
|
+
options.speakerIndex,
|
|
55
|
+
options.sidebarPosition,
|
|
56
|
+
options.sidebarRatio
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
function useGridAnimation(preset = "smooth") {
|
|
60
|
+
return useMemo(() => getSpringConfig(preset), [preset]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const GridContainer = forwardRef(
|
|
64
|
+
function GridContainer2({
|
|
65
|
+
children,
|
|
66
|
+
aspectRatio = "16:9",
|
|
67
|
+
gap = 8,
|
|
68
|
+
count,
|
|
69
|
+
layoutMode = "gallery",
|
|
70
|
+
pinnedIndex,
|
|
71
|
+
speakerIndex,
|
|
72
|
+
sidebarPosition,
|
|
73
|
+
sidebarRatio,
|
|
74
|
+
springPreset = "smooth",
|
|
75
|
+
style,
|
|
76
|
+
className,
|
|
77
|
+
...props
|
|
78
|
+
}, forwardedRef) {
|
|
79
|
+
const internalRef = useRef(null);
|
|
80
|
+
const ref = forwardedRef || internalRef;
|
|
81
|
+
const dimensions = useGridDimensions(ref);
|
|
82
|
+
const childCount = count ?? React.Children.count(children);
|
|
83
|
+
const gridOptions = {
|
|
84
|
+
dimensions,
|
|
85
|
+
count: childCount,
|
|
86
|
+
aspectRatio,
|
|
87
|
+
gap,
|
|
88
|
+
layoutMode,
|
|
89
|
+
pinnedIndex,
|
|
90
|
+
speakerIndex,
|
|
91
|
+
sidebarPosition,
|
|
92
|
+
sidebarRatio
|
|
93
|
+
};
|
|
94
|
+
const grid = useMeetGrid(gridOptions);
|
|
95
|
+
const containerStyle = {
|
|
96
|
+
position: "relative",
|
|
97
|
+
width: "100%",
|
|
98
|
+
height: "100%",
|
|
99
|
+
overflow: "hidden",
|
|
100
|
+
...style
|
|
101
|
+
};
|
|
102
|
+
return /* @__PURE__ */ React.createElement(GridContext.Provider, { value: { dimensions, grid, springPreset } }, /* @__PURE__ */ React.createElement("div", { ref, style: containerStyle, className, ...props }, children));
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
const GridItem = forwardRef(
|
|
106
|
+
function GridItem2({
|
|
107
|
+
index,
|
|
108
|
+
children,
|
|
109
|
+
transition: customTransition,
|
|
110
|
+
disableAnimation = false,
|
|
111
|
+
className,
|
|
112
|
+
style,
|
|
113
|
+
...props
|
|
114
|
+
}, ref) {
|
|
115
|
+
const { grid, springPreset } = useGridContext();
|
|
116
|
+
if (!grid) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const { top, left } = grid.getPosition(index);
|
|
120
|
+
const { width, height } = grid.getItemDimensions(index);
|
|
121
|
+
const isMain = grid.isMainItem(index);
|
|
122
|
+
if (grid.layoutMode === "spotlight" && !isMain) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const springConfig = getSpringConfig(springPreset);
|
|
126
|
+
const transition = customTransition ?? {
|
|
127
|
+
type: springConfig.type,
|
|
128
|
+
stiffness: springConfig.stiffness,
|
|
129
|
+
damping: springConfig.damping
|
|
130
|
+
};
|
|
131
|
+
const animatedStyle = {
|
|
132
|
+
position: "absolute",
|
|
133
|
+
width,
|
|
134
|
+
height,
|
|
135
|
+
top,
|
|
136
|
+
left
|
|
137
|
+
};
|
|
138
|
+
if (disableAnimation) {
|
|
139
|
+
return /* @__PURE__ */ React.createElement(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
ref,
|
|
143
|
+
style: { ...animatedStyle, ...style },
|
|
144
|
+
className,
|
|
145
|
+
"data-grid-index": index,
|
|
146
|
+
"data-grid-main": isMain,
|
|
147
|
+
...props
|
|
148
|
+
},
|
|
149
|
+
children
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return /* @__PURE__ */ React.createElement(
|
|
153
|
+
motion.div,
|
|
154
|
+
{
|
|
155
|
+
ref,
|
|
156
|
+
layout: true,
|
|
157
|
+
initial: false,
|
|
158
|
+
animate: animatedStyle,
|
|
159
|
+
transition,
|
|
160
|
+
style,
|
|
161
|
+
className,
|
|
162
|
+
"data-grid-index": index,
|
|
163
|
+
"data-grid-main": isMain,
|
|
164
|
+
...props
|
|
165
|
+
},
|
|
166
|
+
children
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
const GridOverlay = forwardRef(
|
|
171
|
+
function GridOverlay2({ visible = true, backgroundColor = "rgba(0,0,0,0.5)", children, style, ...props }, ref) {
|
|
172
|
+
if (!visible)
|
|
173
|
+
return null;
|
|
174
|
+
return /* @__PURE__ */ React.createElement(
|
|
175
|
+
"div",
|
|
176
|
+
{
|
|
177
|
+
ref,
|
|
178
|
+
style: {
|
|
179
|
+
position: "absolute",
|
|
180
|
+
inset: 0,
|
|
181
|
+
display: "flex",
|
|
182
|
+
alignItems: "center",
|
|
183
|
+
justifyContent: "center",
|
|
184
|
+
backgroundColor,
|
|
185
|
+
...style
|
|
186
|
+
},
|
|
187
|
+
...props
|
|
188
|
+
},
|
|
189
|
+
children
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thangdevalone/meet-layout-grid-react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React integration for meet-layout-grid with Motion animations",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.mts",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "unbuild",
|
|
26
|
+
"dev": "unbuild --watch",
|
|
27
|
+
"clean": "rimraf dist",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"react",
|
|
33
|
+
"grid",
|
|
34
|
+
"meeting",
|
|
35
|
+
"video",
|
|
36
|
+
"motion",
|
|
37
|
+
"animation"
|
|
38
|
+
],
|
|
39
|
+
"author": "ThangDevAlone",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/thangdevalone/meet-layout-grid"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=18.0.0",
|
|
47
|
+
"react-dom": ">=18.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@thangdevalone/meet-layout-grid-core": "workspace:*",
|
|
51
|
+
"motion": "^11.15.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/react": "^18.2.0",
|
|
55
|
+
"@types/react-dom": "^18.2.0",
|
|
56
|
+
"react": "^18.2.0",
|
|
57
|
+
"react-dom": "^18.2.0",
|
|
58
|
+
"unbuild": "^2.0.0",
|
|
59
|
+
"vitest": "^1.0.0",
|
|
60
|
+
"rimraf": "^5.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|