|
2 | 2 |
|
3 | 3 | A React wrapper component for GridStack that provides better TypeScript support and React integration experience.
|
4 | 4 |
|
| 5 | +Open in [CodeSandbox](https://codesandbox.io/p/sandbox/github/gridstack/gridstack.js/tree/master/react?file=/src/App.tsx) |
| 6 | + |
5 | 7 | ## TODO
|
6 | 8 |
|
7 |
| -- [x] Component mapping |
8 |
| -- [x] SubGrid support |
9 |
| -- [ ] Save and restore layout |
10 |
| -- [ ] Publish to npm |
| 9 | +- [x] Add Widgets |
| 10 | +- [x] Add Sub Grid |
| 11 | +- [x] Nested Sub Grid |
| 12 | +- [x] Remove Widget |
| 13 | +- [x] Copy(Duplicate) Widget |
| 14 | +- [x] Custom handle |
| 15 | +- [ ] Drag between two grid stacks |
11 | 16 |
|
12 |
| -## Basic Usage |
| 17 | +Welcome to give any suggestions and ideas, you can submit an issue or contact me by email. :) |
13 | 18 |
|
14 |
| -This is not an npm package, it's just a demo project. Please copy the relevant code to your project to use it. |
| 19 | +## Usage |
15 | 20 |
|
16 |
| -```tsx |
17 |
| -import { |
18 |
| - GridStackProvider, |
19 |
| - GridStackRender, |
20 |
| - GridStackRenderProvider, |
21 |
| -} from "path/to/lib"; |
22 |
| -import "gridstack/dist/gridstack.css"; |
23 |
| -import "gridstack/dist/gridstack-extra.css"; |
24 |
| -import "path/to/demo.css"; |
25 |
| - |
26 |
| -function Text({ content }: { content: string }) { |
27 |
| - return <div>{content}</div>; |
28 |
| -} |
| 21 | +**Simple** |
29 | 22 |
|
30 |
| -const COMPONENT_MAP = { |
31 |
| - Text, |
32 |
| - // ... other components |
33 |
| -}; |
34 |
| - |
35 |
| -// Grid options |
36 |
| -const gridOptions = { |
37 |
| - acceptWidgets: true, |
38 |
| - margin: 8, |
39 |
| - cellHeight: 50, |
40 |
| - children: [ |
41 |
| - { |
42 |
| - id: "item1", |
43 |
| - h: 2, |
44 |
| - w: 2, |
45 |
| - content: JSON.stringify({ |
46 |
| - name: "Text", |
47 |
| - props: { content: "Item 1" }, |
48 |
| - }), |
49 |
| - }, |
50 |
| - // ... other grid items |
51 |
| - ], |
52 |
| -}; |
| 23 | +Render item with widget id selector. |
53 | 24 |
|
| 25 | +```tsx |
54 | 26 | function App() {
|
| 27 | + const [uncontrolledInitialOptions] = useState<GridStackOptions>({ |
| 28 | + // ... |
| 29 | + children: [ |
| 30 | + { id: "item1", h: 2, w: 2, x: 0, y: 0 }, |
| 31 | + { id: "item2", h: 2, w: 2, x: 2, y: 0 }, |
| 32 | + ], |
| 33 | + }); |
| 34 | + |
55 | 35 | return (
|
56 |
| - <GridStackProvider initialOptions={gridOptions}> |
57 |
| - <!-- Maybe a toolbar here. Access to addWidget and addSubGrid by useGridStackContext() --> |
| 36 | + <GridStackProvider initialOptions={uncontrolledInitialOptions}> |
| 37 | + <Toolbar /> |
58 | 38 |
|
59 |
| - <!-- Grid Stack Root Element --> |
60 |
| - <GridStackRenderProvider> |
61 |
| - <!-- Grid Stack Default Render --> |
62 |
| - <GridStackRender componentMap={COMPONENT_MAP} /> |
63 |
| - </GridStackRenderProvider> |
| 39 | + <GridStackRender> |
| 40 | + <GridStackItem id="item1"> |
| 41 | + <div>hello</div> |
| 42 | + </GridStackItem> |
64 | 43 |
|
65 |
| - <!-- Maybe other UI here --> |
| 44 | + <GridStackItem id="item2"> |
| 45 | + <div>grid</div> |
| 46 | + </GridStackItem> |
| 47 | + </GridStackRender> |
66 | 48 | </GridStackProvider>
|
67 | 49 | );
|
68 | 50 | }
|
69 | 51 | ```
|
70 | 52 |
|
71 |
| -## Advanced Features |
| 53 | +**Advanced** |
72 | 54 |
|
73 |
| -### Toolbar Operations |
| 55 | +Render item with widget map component info. |
74 | 56 |
|
75 |
| -Provide APIs to add new components and sub-grids: |
| 57 | +_ComponentInfoMap is just an example, you can use any way you want to store and retrieve component information._ |
76 | 58 |
|
77 | 59 | ```tsx
|
78 |
| -function Toolbar() { |
79 |
| - const { addWidget, addSubGrid } = useGridStackContext(); |
| 60 | +function App() { |
| 61 | + const [uncontrolledInitialOptions] = useState<GridStackOptions>({ |
| 62 | + // ... |
| 63 | + children: [ |
| 64 | + { id: "item1", h: 2, w: 2, x: 0, y: 0 }, |
| 65 | + { id: "item2", h: 2, w: 2, x: 2, y: 0 }, |
| 66 | + ], |
| 67 | + }); |
| 68 | + |
| 69 | + const [initialComponentInfoMap] = useState<Record<string, ComponentInfo>>( |
| 70 | + () => ({ |
| 71 | + item1: { component: "Text", serializableProps: { content: "Text" } }, |
| 72 | + item2: { |
| 73 | + component: "ComplexCard", |
| 74 | + serializableProps: { title: "Complex Card", color: "red" }, |
| 75 | + }, |
| 76 | + }) |
| 77 | + ); |
80 | 78 |
|
81 | 79 | return (
|
82 |
| - <div> |
83 |
| - <button onClick={() => addWidget(/* ... */)}>Add Component</button> |
84 |
| - <button onClick={() => addSubGrid(/* ... */)}>Add SubGrid</button> |
85 |
| - </div> |
| 80 | + <ComponentInfoMapProvider initialComponentInfoMap={initialComponentInfoMap}> |
| 81 | + <GridStackProvider initialOptions={uncontrolledInitialOptions}> |
| 82 | + <Toolbar /> |
| 83 | + |
| 84 | + <GridStackRender> |
| 85 | + <DynamicGridStackItems /> |
| 86 | + </GridStackRender> |
| 87 | + </GridStackProvider> |
| 88 | + </ComponentInfoMapProvider> |
| 89 | + ); |
| 90 | +} |
| 91 | + |
| 92 | +export function DynamicGridStackItems() { |
| 93 | + const { componentInfoMap } = useComponentInfoMap(); |
| 94 | + |
| 95 | + return ( |
| 96 | + <> |
| 97 | + {Array.from(componentInfoMap.entries()).map( |
| 98 | + ([widgetId, componentInfo]) => { |
| 99 | + const Component = COMPONENT_MAP[componentInfo.component]; |
| 100 | + if (!Component) { |
| 101 | + throw new Error(`Component ${componentInfo.component} not found`); |
| 102 | + } |
| 103 | + |
| 104 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 105 | + const props = componentInfo.serializableProps as any; |
| 106 | + |
| 107 | + if (componentInfo.component === "ComplexCard") { |
| 108 | + return ( |
| 109 | + <GridStackItem key={widgetId} id={widgetId}> |
| 110 | + <ComplexCardEditableWrapper |
| 111 | + key={`complex-card-editable-wrapper-${widgetId}`} |
| 112 | + serializableProps={componentInfo.serializableProps} |
| 113 | + > |
| 114 | + <Component {...props} key={`component-${widgetId}`} /> |
| 115 | + </ComplexCardEditableWrapper> |
| 116 | + </GridStackItem> |
| 117 | + ); |
| 118 | + } |
| 119 | + |
| 120 | + return ( |
| 121 | + <GridStackItem key={widgetId} id={widgetId}> |
| 122 | + <Component {...props} key={`component-${widgetId}`} /> |
| 123 | + </GridStackItem> |
| 124 | + ); |
| 125 | + } |
| 126 | + )} |
| 127 | + </> |
86 | 128 | );
|
87 | 129 | }
|
88 | 130 | ```
|
89 | 131 |
|
90 |
| -### Layout Saving |
| 132 | +**Experimental** |
91 | 133 |
|
92 |
| -Get the current layout: |
| 134 | +Render item with custom handle. |
93 | 135 |
|
94 | 136 | ```tsx
|
95 |
| -const { saveOptions } = useGridStackContext(); |
96 |
| - |
97 |
| -const currentLayout = saveOptions(); |
| 137 | +<GridStackItem id="xxx"> |
| 138 | + <GridStackHandleReInitializer> |
| 139 | + <button className={CUSTOM_DRAGGABLE_HANDLE_CLASSNAME}> |
| 140 | + Handle ONLY HERE |
| 141 | + </button> |
| 142 | + </GridStackHandleReInitializer> |
| 143 | +</GridStackItem> |
98 | 144 | ```
|
99 | 145 |
|
100 | 146 | ## API Reference
|
101 | 147 |
|
102 |
| -### GridStackProvider |
| 148 | +### Components |
103 | 149 |
|
104 |
| -The main context provider, accepts the following properties: |
| 150 | +#### GridStackProvider |
105 | 151 |
|
106 |
| -- `initialOptions`: Initial configuration options for GridStack |
| 152 | +Top-level component that provides GridStack context. |
107 | 153 |
|
108 |
| -### GridStackRender |
| 154 | +```typescript |
| 155 | +type GridStackProviderProps = { |
| 156 | + initialOptions: GridStackOptions; // GridStack initialization options |
| 157 | + children: React.ReactNode; |
| 158 | +}; |
| 159 | +``` |
109 | 160 |
|
110 |
| -The core component for rendering the grid, accepts the following properties: |
| 161 | +#### GridStackRender |
| 162 | + |
| 163 | +Render GridStack root container component. |
| 164 | + |
| 165 | +```typescript |
| 166 | +type GridStackRenderProps = { |
| 167 | + children: React.ReactNode; |
| 168 | +}; |
| 169 | +``` |
111 | 170 |
|
112 |
| -- `componentMap`: A mapping from component names to actual React components |
| 171 | +#### GridStackItem |
| 172 | + |
| 173 | +Component representing a single grid item. |
| 174 | + |
| 175 | +```typescript |
| 176 | +type GridStackItemProps = { |
| 177 | + id: string; // Grid item unique identifier |
| 178 | + children: React.ReactNode; |
| 179 | +}; |
| 180 | +``` |
| 181 | + |
| 182 | +#### GridStackHandleReInitializer |
| 183 | + |
| 184 | +Experimental component for reinitializing the drag handle of a grid item. |
| 185 | + |
| 186 | +```typescript |
| 187 | +type GridStackHandleReInitializerProps = { |
| 188 | + children: React.ReactNode; |
| 189 | +}; |
| 190 | +``` |
| 191 | + |
| 192 | +### Contexts |
| 193 | + |
| 194 | +#### GridStackContext |
| 195 | + |
| 196 | +Provide GridStack core functionality context. |
| 197 | + |
| 198 | +```typescript |
| 199 | +interface GridStackContextType { |
| 200 | + initialOptions: GridStackOptions; |
| 201 | + addWidget: (widget: GridStackWidget) => void; |
| 202 | + removeWidget: (el: GridStackElement) => void; |
| 203 | + saveOptions: () => ReturnType<GridStack["save"]> | undefined; |
| 204 | + |
| 205 | + _gridStack: { |
| 206 | + value: GridStack | null; |
| 207 | + set: React.Dispatch<React.SetStateAction<GridStack | null>>; |
| 208 | + }; |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +#### GridStackItemContext |
| 213 | + |
| 214 | +Provide single grid item functionality context. |
| 215 | + |
| 216 | +```typescript |
| 217 | +type GridStackItemContextType = { |
| 218 | + id: string; |
| 219 | + // Native methods |
| 220 | + remove: () => void; |
| 221 | + update: (opt: GridStackWidget) => void; |
| 222 | + |
| 223 | + // Extended methods |
| 224 | + getBounds: () => { |
| 225 | + current: { x?: number; y?: number; w?: number; h?: number }; |
| 226 | + original: { x?: number; y?: number; w?: number; h?: number }; |
| 227 | + } | null; |
| 228 | + setSize: (size: { w: number; h: number }) => void; |
| 229 | + setPosition: (position: { x: number; y: number }) => void; |
| 230 | +}; |
| 231 | +``` |
| 232 | + |
| 233 | +#### GridStackRenderContext |
| 234 | + |
| 235 | +Provide rendering related functionality context. |
| 236 | + |
| 237 | +```typescript |
| 238 | +type GridStackRenderContextType = { |
| 239 | + getWidgetContainer: (widgetId: string) => HTMLElement | null; |
| 240 | +}; |
| 241 | +``` |
113 | 242 |
|
114 | 243 | ### Hooks
|
115 | 244 |
|
116 |
| -- `useGridStackContext()`: Access GridStack context and operations |
117 |
| - - `addWidget`: Add a new component |
118 |
| - - `addSubGrid`: Add a new sub-grid |
119 |
| - - `saveOptions`: Save current layout |
120 |
| - - `initialOptions`: Initial configuration options |
| 245 | +#### useGridStackContext |
| 246 | + |
| 247 | +Get GridStack context. |
| 248 | + |
| 249 | +```typescript |
| 250 | +function useGridStackContext(): GridStackContextType; |
| 251 | +``` |
| 252 | + |
| 253 | +#### useGridStackItemContext |
| 254 | + |
| 255 | +Get grid item context. |
| 256 | + |
| 257 | +```typescript |
| 258 | +function useGridStackItemContext(): GridStackItemContextType; |
| 259 | +``` |
| 260 | + |
| 261 | +#### useGridStackRenderContext |
| 262 | + |
| 263 | +Get rendering context. |
| 264 | + |
| 265 | +```typescript |
| 266 | +function useGridStackRenderContext(): GridStackRenderContextType; |
| 267 | +``` |
| 268 | + |
| 269 | +### Type Exports |
| 270 | + |
| 271 | +```typescript |
| 272 | +export type { |
| 273 | + GridStackContextType, |
| 274 | + GridStackProviderProps, |
| 275 | + GridStackRenderContextType, |
| 276 | + GridStackRenderProps, |
| 277 | + GridStackItemProps, |
| 278 | + GridStackItemContextType, |
| 279 | + GridStackHandleReInitializerProps, |
| 280 | +}; |
| 281 | +``` |
0 commit comments