Skip to content

Commit f6763b8

Browse files
committed
feat: more update react wrapper
1 parent 3c57a53 commit f6763b8

15 files changed

+357
-432
lines changed

react/lib/constants.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { GridStackOptions } from 'gridstack';
2+
3+
export const BREAKPOINTS = [
4+
{ c: 1, w: 700 },
5+
{ c: 3, w: 850 },
6+
{ c: 6, w: 950 },
7+
{ c: 8, w: 1100 },
8+
];
9+
10+
export const SUB_GRID_OPTIONS: GridStackOptions = {
11+
acceptWidgets: true,
12+
column: 12,
13+
columnOpts: {
14+
breakpoints: BREAKPOINTS,
15+
layout: 'moveScale',
16+
},
17+
margin: 8,
18+
minRow: 2,
19+
} as const;
20+
21+
export const GRID_OPTIONS: GridStackOptions = {
22+
acceptWidgets: true,
23+
columnOpts: {
24+
breakpointForWindow: true,
25+
breakpoints: BREAKPOINTS,
26+
layout: 'moveScale',
27+
},
28+
float: false,
29+
margin: 8,
30+
subGridOpts: SUB_GRID_OPTIONS,
31+
} as const;

react/lib/gridstack-context.tsx

+65-40
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,86 @@
1-
// gridstack-context.tsx
2-
"use client";
3-
4-
import * as React from "react";
5-
import type { GridStack } from "gridstack";
6-
import "gridstack/dist/gridstack-extra.css";
7-
import "gridstack/dist/gridstack.css";
8-
import type { ItemRefType } from "./gridstack-item";
1+
import { GridStack, GridStackWidget,GridStackOptions } from 'gridstack';
2+
import React, {
3+
createContext,
4+
PropsWithChildren,
5+
useCallback,
6+
useLayoutEffect,
7+
useMemo,
8+
useRef,
9+
useState,
10+
} from 'react';
11+
import isEqual from 'react-fast-compare';
912

1013
type GridStackContextType = {
11-
grid: GridStack | null | undefined;
12-
setGrid: React.Dispatch<React.SetStateAction<GridStack | null>>;
13-
addItemRefToList: (id: string, ref: ItemRefType) => void;
14-
removeItemRefFromList: (id: string) => void;
15-
itemRefList: ItemRefListType;
16-
getItemRefFromListById: (id: string) => ItemRefType | null;
14+
getWidgetContent: (widgetId: string) => HTMLElement | null;
1715
};
1816

19-
type ItemRefListType = {
20-
id: string;
21-
ref: ItemRefType;
22-
}[];
17+
interface GridstackProviderProps extends PropsWithChildren {
18+
options: GridStackOptions;
19+
}
20+
21+
export const GridstackContext = createContext<GridStackContextType | null>(null);
2322

24-
export const GridstackContext = React.createContext<GridStackContextType | null>(null);
23+
export const GridstackProvider = ({ children, options }: GridstackProviderProps) => {
24+
const widgetContentRef = useRef<Record<string, HTMLElement>>({});
25+
const containerRef = useRef<HTMLDivElement>(null);
26+
const optionsRef = useRef<GridStackOptions>(options);
2527

26-
export const GridstackProvider = ({ children }: { children: React.ReactNode }) => {
27-
const [grid, setGrid] = React.useState<GridStack | null>(null);
28-
const [itemRefList, setItemRefList] = React.useState<ItemRefListType>([]);
28+
const [parentGrid, setParentGrid] = useState<GridStack | null>(null);
2929

30-
const addItemRefToList = React.useCallback((id: string, ref: ItemRefType) => {
31-
setItemRefList((prev) => [...prev, { id, ref }]);
30+
const renderCBFn = useCallback((element: HTMLElement, widget: GridStackWidget) => {
31+
if (widget.id) {
32+
widgetContentRef.current[widget.id] = element;
33+
}
3234
}, []);
3335

34-
const removeItemRefFromList = React.useCallback((id: string) => {
35-
setItemRefList((prev) => prev.filter((item) => item.id !== id));
36+
const getWidgetContent = useCallback((widgetId: string) => {
37+
return widgetContentRef.current[widgetId] || null;
3638
}, []);
3739

38-
const getItemRefFromListById = React.useCallback((id: string) => {
39-
const item = itemRefList.find((item) => item.id === id);
40-
return item?.ref ?? null;
41-
}, [itemRefList]);
40+
const initGrid = useCallback(() => {
41+
if (containerRef.current) {
42+
GridStack.renderCB = renderCBFn;
43+
return GridStack.init(optionsRef.current, containerRef.current);
44+
}
45+
return null;
46+
}, [renderCBFn]);
47+
48+
useLayoutEffect(() => {
49+
if (!isEqual(options, optionsRef.current) && parentGrid) {
50+
try {
51+
parentGrid.removeAll(false);
52+
parentGrid.destroy(false);
53+
widgetContentRef.current = {};
54+
optionsRef.current = options;
55+
56+
setParentGrid(initGrid());
57+
} catch (e) {
58+
console.error("Error reinitializing gridstack", e);
59+
}
60+
}
61+
}, [options, parentGrid, initGrid]);
62+
63+
useLayoutEffect(() => {
64+
if (!parentGrid) {
65+
try {
66+
setParentGrid(initGrid());
67+
} catch (e) {
68+
console.error("Error initializing gridstack", e);
69+
}
70+
}
71+
}, [parentGrid, initGrid]);
4272

43-
// Memoize the context value to prevent unnecessary re-renders
44-
const value = React.useMemo(
73+
const value = useMemo(
4574
() => ({
46-
grid,
47-
setGrid,
48-
addItemRefToList,
49-
removeItemRefFromList,
50-
itemRefList,
51-
getItemRefFromListById,
75+
getWidgetContent,
5276
}),
53-
[grid, itemRefList, addItemRefToList, removeItemRefFromList, getItemRefFromListById]
77+
// parentGrid is required to reinitialize the grid when the options change
78+
[getWidgetContent, parentGrid],
5479
);
5580

5681
return (
5782
<GridstackContext.Provider value={value}>
58-
{children}
83+
<div ref={containerRef}>{parentGrid ? children : null}</div>
5984
</GridstackContext.Provider>
6085
);
6186
};

react/lib/gridstack-grid.tsx

-44
This file was deleted.

react/lib/gridstack-item.tsx

+12-81
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,22 @@
1-
"use client";
2-
// gridstack-item.tsx
1+
import { type GridItemHTMLElement } from 'gridstack';
2+
import React, { PropsWithChildren } from 'react';
3+
import { createPortal } from 'react-dom';
34

4-
import type {
5-
GridItemHTMLElement,
5+
import { useGridstackContext } from './use-gridstack-context';
66

7-
GridStackWidget,
8-
} from "gridstack";
9-
import * as React from "react";
10-
import {useGridstackContext} from "./use-gridstack-context";
11-
12-
13-
14-
interface GridstackItemComponentProps {
15-
initOptions?: GridStackWidget;
7+
export interface GridstackItemProps extends PropsWithChildren {
168
id: string;
17-
children: React.ReactNode;
18-
className?: string;
199
}
2010

2111
export type ItemRefType = React.MutableRefObject<GridItemHTMLElement | null>;
2212

23-
/**
24-
* Component for rendering a grid item in a Gridstack layout.
25-
*
26-
* @component
27-
* @param {GridstackItemComponentProps} props - The component props.
28-
* @param {GridStackWidget} props.initOptions - The initial options for the grid item.
29-
* @param {string} props.id - The unique identifier for the grid item.
30-
* @param {ReactNode} props.children - The content to be rendered inside the grid item.
31-
* @param {string} props.className - The CSS class name for the grid item.
32-
* @returns {JSX.Element} The rendered grid item component.
33-
*/
34-
export const GridstackItemComponent = ({
35-
initOptions,
36-
id,
37-
children,
38-
className,
39-
}: GridstackItemComponentProps) => {
40-
const containerRef = React.useRef<HTMLDivElement>(null);
41-
const optionsRef = React.useRef<GridStackWidget | undefined>(initOptions);
42-
const { grid, addItemRefToList, removeItemRefFromList } = useGridstackContext();
43-
const itemRef = React.useRef<GridItemHTMLElement | null>(null);
44-
45-
// Update the optionsRef when initOptions changes
46-
React.useEffect(() => {
47-
optionsRef.current = initOptions;
48-
}, [initOptions]);
49-
50-
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
51-
React.useLayoutEffect(() => {
52-
if (!grid || !containerRef.current) return;
53-
if (grid && containerRef.current) {
54-
// Initialize the widget
55-
56-
grid.batchUpdate(true);
57-
itemRef.current = grid.addWidget(containerRef.current, optionsRef.current);
58-
grid.batchUpdate(false);
59-
60-
addItemRefToList(id, itemRef);
61-
62-
63-
// Cleanup function to remove the widget
64-
return () => {
65-
if (grid && itemRef.current) {
66-
try {
67-
grid.batchUpdate(true);
68-
grid.removeWidget(itemRef.current, false);
69-
grid.batchUpdate(false);
70-
71-
removeItemRefFromList(id);
72-
73-
74-
} catch (error) {
75-
console.error("Error removing widget", error);
76-
//! Doing nothing here is a bad practice, but we don't want to crash the app (Temporary fix)
77-
// Do nothing
78-
}
79-
}
80-
};
81-
}
13+
export const GridstackItem = ({ id, children }: GridstackItemProps) => {
14+
const { getWidgetContent } = useGridstackContext();
15+
const widgetContent = getWidgetContent(id);
8216

83-
// eslint-disable-next-line react-hooks/exhaustive-deps
84-
}, [grid]);
17+
if (!widgetContent) {
18+
return null;
19+
}
8520

86-
return (
87-
<div ref={containerRef} id="">
88-
<div className={`w-full h-full ${className}`}>{children}</div>
89-
</div>
90-
);
21+
return createPortal(children, widgetContent);
9122
};

react/lib/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from './gridstack-item';
2-
export * from './gridstack-grid';
3-
export * from './gridstack-context';
4-
export * from './use-gridstack-context';
1+
2+
export { GridstackProvider } from './gridstack-context';
3+
export { GridstackItem } from './gridstack-item';
4+
export { useGridstackContext } from './use-gridstack-context';

react/lib/use-gridstack-context.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useContext } from "react";
2+
3+
import { GridstackContext } from "./gridstack-context";
4+
5+
export const useGridstackContext = () => {
6+
const gridstackContext = useContext(GridstackContext);
7+
if (!gridstackContext) {
8+
throw new Error('useGridstack must be used within a GridstackProvider');
9+
}
10+
return gridstackContext;
11+
};

react/lib/use-gridstack-context.tsx

-11
This file was deleted.

react/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"dependencies": {
1414
"gridstack": "^11.0.1",
1515
"react": "^18.3.1",
16-
"react-dom": "^18.3.1"
16+
"react-dom": "^18.3.1",
17+
"react-fast-compare": "^3.2.2"
1718
},
1819
"devDependencies": {
1920
"@eslint/js": "^9.9.0",

react/src/App.css

-42
This file was deleted.

0 commit comments

Comments
 (0)