react-native-smart-grid 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +554 -0
  3. package/lib/module/components/DragLayer.js +71 -0
  4. package/lib/module/components/DragLayer.js.map +1 -0
  5. package/lib/module/components/DraggableTile.js +79 -0
  6. package/lib/module/components/DraggableTile.js.map +1 -0
  7. package/lib/module/components/GhostTile.js +37 -0
  8. package/lib/module/components/GhostTile.js.map +1 -0
  9. package/lib/module/components/GridTile.js +25 -0
  10. package/lib/module/components/GridTile.js.map +1 -0
  11. package/lib/module/components/ResizeHandle.js +72 -0
  12. package/lib/module/components/ResizeHandle.js.map +1 -0
  13. package/lib/module/components/SmartGrid.js +363 -0
  14. package/lib/module/components/SmartGrid.js.map +1 -0
  15. package/lib/module/context/GridDragContext.js +130 -0
  16. package/lib/module/context/GridDragContext.js.map +1 -0
  17. package/lib/module/engine/GridEngine.js +148 -0
  18. package/lib/module/engine/GridEngine.js.map +1 -0
  19. package/lib/module/engine/autoArrange.js +54 -0
  20. package/lib/module/engine/autoArrange.js.map +1 -0
  21. package/lib/module/engine/collisions.js +67 -0
  22. package/lib/module/engine/collisions.js.map +1 -0
  23. package/lib/module/hooks/useTileGesture.js +62 -0
  24. package/lib/module/hooks/useTileGesture.js.map +1 -0
  25. package/lib/module/index.js +9 -0
  26. package/lib/module/index.js.map +1 -0
  27. package/lib/module/layout/LayoutCalculator.js +29 -0
  28. package/lib/module/layout/LayoutCalculator.js.map +1 -0
  29. package/lib/module/package.json +1 -0
  30. package/lib/module/types.js +2 -0
  31. package/lib/module/types.js.map +1 -0
  32. package/lib/module/utils/pixelToGrid.js +22 -0
  33. package/lib/module/utils/pixelToGrid.js.map +1 -0
  34. package/lib/typescript/package.json +1 -0
  35. package/lib/typescript/src/components/DragLayer.d.ts +11 -0
  36. package/lib/typescript/src/components/DragLayer.d.ts.map +1 -0
  37. package/lib/typescript/src/components/DraggableTile.d.ts +14 -0
  38. package/lib/typescript/src/components/DraggableTile.d.ts.map +1 -0
  39. package/lib/typescript/src/components/GhostTile.d.ts +9 -0
  40. package/lib/typescript/src/components/GhostTile.d.ts.map +1 -0
  41. package/lib/typescript/src/components/GridTile.d.ts +9 -0
  42. package/lib/typescript/src/components/GridTile.d.ts.map +1 -0
  43. package/lib/typescript/src/components/ResizeHandle.d.ts +9 -0
  44. package/lib/typescript/src/components/ResizeHandle.d.ts.map +1 -0
  45. package/lib/typescript/src/components/SmartGrid.d.ts +214 -0
  46. package/lib/typescript/src/components/SmartGrid.d.ts.map +1 -0
  47. package/lib/typescript/src/context/GridDragContext.d.ts +44 -0
  48. package/lib/typescript/src/context/GridDragContext.d.ts.map +1 -0
  49. package/lib/typescript/src/engine/GridEngine.d.ts +35 -0
  50. package/lib/typescript/src/engine/GridEngine.d.ts.map +1 -0
  51. package/lib/typescript/src/engine/autoArrange.d.ts +4 -0
  52. package/lib/typescript/src/engine/autoArrange.d.ts.map +1 -0
  53. package/lib/typescript/src/engine/collisions.d.ts +3 -0
  54. package/lib/typescript/src/engine/collisions.d.ts.map +1 -0
  55. package/lib/typescript/src/hooks/useTileGesture.d.ts +13 -0
  56. package/lib/typescript/src/hooks/useTileGesture.d.ts.map +1 -0
  57. package/lib/typescript/src/index.d.ts +10 -0
  58. package/lib/typescript/src/index.d.ts.map +1 -0
  59. package/lib/typescript/src/layout/LayoutCalculator.d.ts +15 -0
  60. package/lib/typescript/src/layout/LayoutCalculator.d.ts.map +1 -0
  61. package/lib/typescript/src/types.d.ts +105 -0
  62. package/lib/typescript/src/types.d.ts.map +1 -0
  63. package/lib/typescript/src/utils/pixelToGrid.d.ts +9 -0
  64. package/lib/typescript/src/utils/pixelToGrid.d.ts.map +1 -0
  65. package/package.json +161 -0
  66. package/src/components/DragLayer.tsx +71 -0
  67. package/src/components/DraggableTile.tsx +88 -0
  68. package/src/components/GhostTile.tsx +42 -0
  69. package/src/components/GridTile.tsx +27 -0
  70. package/src/components/ResizeHandle.tsx +74 -0
  71. package/src/components/SmartGrid.tsx +506 -0
  72. package/src/context/GridDragContext.tsx +191 -0
  73. package/src/engine/GridEngine.ts +148 -0
  74. package/src/engine/autoArrange.ts +59 -0
  75. package/src/engine/collisions.ts +87 -0
  76. package/src/hooks/useTileGesture.ts +88 -0
  77. package/src/index.tsx +29 -0
  78. package/src/layout/LayoutCalculator.ts +50 -0
  79. package/src/types.ts +113 -0
  80. package/src/utils/pixelToGrid.ts +31 -0
package/package.json ADDED
@@ -0,0 +1,161 @@
1
+ {
2
+ "name": "react-native-smart-grid",
3
+ "version": "0.1.0",
4
+ "description": "Draggable variable-sized tile grid for React Native",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "import": {
12
+ "default": "./lib/module/index.js"
13
+ },
14
+ "default": "./lib/module/index.js"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "files": [
19
+ "src",
20
+ "lib",
21
+ "android",
22
+ "ios",
23
+ "cpp",
24
+ "*.podspec",
25
+ "react-native.config.js",
26
+ "!ios/build",
27
+ "!android/build",
28
+ "!android/gradle",
29
+ "!android/gradlew",
30
+ "!android/gradlew.bat",
31
+ "!android/local.properties",
32
+ "!**/__tests__",
33
+ "!**/__fixtures__",
34
+ "!**/__mocks__",
35
+ "!**/.*"
36
+ ],
37
+ "scripts": {
38
+ "example": "yarn workspace react-native-smart-grid-example",
39
+ "clean": "del-cli lib",
40
+ "prepare": "bob build",
41
+ "typecheck": "tsc",
42
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
43
+ "test": "jest"
44
+ },
45
+ "keywords": [
46
+ "react-native",
47
+ "grid",
48
+ "drag",
49
+ "drag-and-drop",
50
+ "draggable",
51
+ "tile",
52
+ "masonry",
53
+ "dashboard",
54
+ "layout",
55
+ "ios",
56
+ "android"
57
+ ],
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/zabloona/react-native-smart-grid.git"
61
+ },
62
+ "author": "Admin <zabloona@gmail.com> (https://github.com/zabloona)",
63
+ "license": "MIT",
64
+ "bugs": {
65
+ "url": "https://github.com/zabloona/react-native-smart-grid/issues"
66
+ },
67
+ "homepage": "https://github.com/zabloona/react-native-smart-grid#readme",
68
+ "publishConfig": {
69
+ "registry": "https://registry.npmjs.org/"
70
+ },
71
+ "devDependencies": {
72
+ "@eslint/compat": "^2.1.0",
73
+ "@eslint/eslintrc": "^3.3.5",
74
+ "@eslint/js": "^10.0.1",
75
+ "@jest/globals": "^29.7.0",
76
+ "@react-native/babel-preset": "0.85.0",
77
+ "@react-native/eslint-config": "0.85.0",
78
+ "@react-native/jest-preset": "0.85.0",
79
+ "@types/react": "^19.2.0",
80
+ "del-cli": "^7.0.0",
81
+ "eslint": "^9.39.4",
82
+ "eslint-config-prettier": "^10.1.8",
83
+ "eslint-plugin-ft-flow": "^3.0.11",
84
+ "eslint-plugin-prettier": "^5.5.6",
85
+ "jest": "^29.7.0",
86
+ "prettier": "^3.8.3",
87
+ "react": "19.2.0",
88
+ "react-native": "0.83.6",
89
+ "react-native-builder-bob": "^0.42.1",
90
+ "react-native-gesture-handler": "^3.0.1",
91
+ "react-native-reanimated": "^4.4.1",
92
+ "turbo": "^2.9.16",
93
+ "typescript": "^6.0.3"
94
+ },
95
+ "peerDependencies": {
96
+ "react": "*",
97
+ "react-native": "*",
98
+ "react-native-gesture-handler": ">=2.0.0",
99
+ "react-native-reanimated": ">=3.0.0"
100
+ },
101
+ "peerDependenciesMeta": {
102
+ "react-native-gesture-handler": {
103
+ "optional": false
104
+ },
105
+ "react-native-reanimated": {
106
+ "optional": false
107
+ }
108
+ },
109
+ "workspaces": [
110
+ "example"
111
+ ],
112
+ "packageManager": "yarn@4.11.0",
113
+ "react-native-builder-bob": {
114
+ "source": "src",
115
+ "output": "lib",
116
+ "targets": [
117
+ [
118
+ "module",
119
+ {
120
+ "esm": true
121
+ }
122
+ ],
123
+ [
124
+ "typescript",
125
+ {
126
+ "project": "tsconfig.build.json"
127
+ }
128
+ ]
129
+ ]
130
+ },
131
+ "prettier": {
132
+ "quoteProps": "consistent",
133
+ "singleQuote": true,
134
+ "tabWidth": 2,
135
+ "trailingComma": "es5",
136
+ "useTabs": false
137
+ },
138
+ "jest": {
139
+ "preset": "@react-native/jest-preset",
140
+ "transformIgnorePatterns": [
141
+ "node_modules/(?!(react-native|@react-native)/)"
142
+ ],
143
+ "moduleNameMapper": {
144
+ "react-native-reanimated": "<rootDir>/src/__mocks__/reanimated.js",
145
+ "react-native-gesture-handler": "<rootDir>/src/__mocks__/gesture-handler.js"
146
+ },
147
+ "modulePathIgnorePatterns": [
148
+ "<rootDir>/example/node_modules",
149
+ "<rootDir>/lib/"
150
+ ]
151
+ },
152
+ "create-react-native-library": {
153
+ "type": "library",
154
+ "languages": "js",
155
+ "tools": [
156
+ "eslint",
157
+ "jest"
158
+ ],
159
+ "version": "0.62.2"
160
+ }
161
+ }
@@ -0,0 +1,71 @@
1
+ import { memo, useEffect } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withSpring,
7
+ } from 'react-native-reanimated';
8
+ import { useGridDrag } from '../context/GridDragContext';
9
+ import type { Tile } from '../types';
10
+ import type { PixelRect } from '../layout/LayoutCalculator';
11
+
12
+ const LIFT_SPRING = { damping: 15, stiffness: 300, mass: 0.4 };
13
+
14
+ type Props<TData> = {
15
+ tile: Tile<TData>;
16
+ initialRect: PixelRect;
17
+ children: React.ReactNode;
18
+ };
19
+
20
+ function DragLayerInner<TData>({ tile: _tile, initialRect, children }: Props<TData>) {
21
+ const { dragAbsX, dragAbsY, containerPageX, containerPageY, scrollYRef } = useGridDrag();
22
+
23
+ const scale = useSharedValue(1);
24
+ const opacity = useSharedValue(0);
25
+
26
+ useEffect(() => {
27
+ scale.value = withSpring(1.06, LIFT_SPRING);
28
+ opacity.value = withSpring(1, LIFT_SPRING);
29
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
30
+
31
+ const animStyle = useAnimatedStyle(() => {
32
+ const x = dragAbsX.value - containerPageX.current - initialRect.width / 2;
33
+ const y =
34
+ dragAbsY.value -
35
+ containerPageY.current +
36
+ scrollYRef.current -
37
+ initialRect.height / 2;
38
+ return {
39
+ opacity: opacity.value,
40
+ transform: [{ translateX: x }, { translateY: y }, { scale: scale.value }],
41
+ };
42
+ });
43
+
44
+ return (
45
+ <Animated.View
46
+ style={[
47
+ styles.layer,
48
+ { width: initialRect.width, height: initialRect.height },
49
+ animStyle,
50
+ ]}
51
+ >
52
+ {children}
53
+ </Animated.View>
54
+ );
55
+ }
56
+
57
+ export const DragLayer = memo(DragLayerInner) as typeof DragLayerInner;
58
+
59
+ const styles = StyleSheet.create({
60
+ layer: {
61
+ position: 'absolute',
62
+ top: 0,
63
+ left: 0,
64
+ zIndex: 999,
65
+ shadowColor: '#000',
66
+ shadowOffset: { width: 0, height: 10 },
67
+ shadowOpacity: 0.4,
68
+ shadowRadius: 16,
69
+ elevation: 16,
70
+ },
71
+ });
@@ -0,0 +1,88 @@
1
+ import React, { memo, useEffect } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import { GestureDetector } from 'react-native-gesture-handler';
4
+ import Animated, {
5
+ useSharedValue,
6
+ useAnimatedStyle,
7
+ withSpring,
8
+ } from 'react-native-reanimated';
9
+ import { useGridDrag } from '../context/GridDragContext';
10
+ import { useTileGesture } from '../hooks/useTileGesture';
11
+ import { ResizeHandle } from './ResizeHandle';
12
+ import type { PlacedTile, GridConfig } from '../types';
13
+ import type { PixelRect } from '../layout/LayoutCalculator';
14
+
15
+ const SPRING = { damping: 20, stiffness: 220, mass: 0.5 };
16
+
17
+ type Props<TData> = {
18
+ tile: PlacedTile<TData>;
19
+ rect: PixelRect;
20
+ config: GridConfig;
21
+ containerWidth: number;
22
+ children: React.ReactNode;
23
+ };
24
+
25
+ function DraggableTileInner<TData>({
26
+ tile,
27
+ rect,
28
+ config,
29
+ containerWidth,
30
+ children,
31
+ }: Props<TData>) {
32
+ const { activeTile, isEditing } = useGridDrag();
33
+ const { gesture } = useTileGesture({ tile, rect, config, containerWidth });
34
+ const isActive = activeTile?.id === tile.id;
35
+
36
+ const animX = useSharedValue(rect.x);
37
+ const animY = useSharedValue(rect.y);
38
+ const animW = useSharedValue(rect.width);
39
+ const animH = useSharedValue(rect.height);
40
+
41
+ useEffect(() => {
42
+ animX.value = withSpring(rect.x, SPRING);
43
+ animY.value = withSpring(rect.y, SPRING);
44
+ animW.value = withSpring(rect.width, SPRING);
45
+ animH.value = withSpring(rect.height, SPRING);
46
+ }, [rect.x, rect.y, rect.width, rect.height]); // eslint-disable-line react-hooks/exhaustive-deps
47
+
48
+ const animStyle = useAnimatedStyle(() => ({
49
+ left: animX.value,
50
+ top: animY.value,
51
+ width: animW.value,
52
+ height: animH.value,
53
+ }));
54
+
55
+ return (
56
+ <GestureDetector gesture={gesture}>
57
+ <Animated.View
58
+ style={[
59
+ styles.tile,
60
+ animStyle,
61
+ isActive && styles.tileActive,
62
+ isEditing && styles.tileEditing,
63
+ ]}
64
+ >
65
+ {children}
66
+ {isEditing && !tile.locked && (
67
+ <ResizeHandle tile={tile} config={config} containerWidth={containerWidth} />
68
+ )}
69
+ </Animated.View>
70
+ </GestureDetector>
71
+ );
72
+ }
73
+
74
+ export const DraggableTile = memo(DraggableTileInner) as typeof DraggableTileInner;
75
+
76
+ const styles = StyleSheet.create({
77
+ tile: {
78
+ position: 'absolute',
79
+ },
80
+ tileActive: {
81
+ opacity: 0.3,
82
+ },
83
+ tileEditing: {
84
+ borderWidth: 1,
85
+ borderColor: 'rgba(255,255,255,0.3)',
86
+ borderRadius: 12,
87
+ },
88
+ });
@@ -0,0 +1,42 @@
1
+ import { memo } from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import type { PlacedTile, GridConfig } from '../types';
4
+ import { tileToPixelRect } from '../layout/LayoutCalculator';
5
+ import { useGridDrag } from '../context/GridDragContext';
6
+
7
+ type Props = {
8
+ activeTile: PlacedTile;
9
+ config: GridConfig;
10
+ containerWidth: number;
11
+ };
12
+
13
+ export const GhostTile = memo(function GhostTile({
14
+ activeTile,
15
+ config,
16
+ containerWidth,
17
+ }: Props) {
18
+ const { ghostPosition } = useGridDrag();
19
+ if (!ghostPosition) return null;
20
+
21
+ const rect = tileToPixelRect(ghostPosition, activeTile.size, config, containerWidth);
22
+
23
+ return (
24
+ <View
25
+ style={[
26
+ styles.ghost,
27
+ { left: rect.x, top: rect.y, width: rect.width, height: rect.height },
28
+ ]}
29
+ />
30
+ );
31
+ });
32
+
33
+ const styles = StyleSheet.create({
34
+ ghost: {
35
+ position: 'absolute',
36
+ borderRadius: 12,
37
+ borderWidth: 2,
38
+ borderColor: 'rgba(255,255,255,0.4)',
39
+ borderStyle: 'dashed',
40
+ backgroundColor: 'rgba(255,255,255,0.08)',
41
+ },
42
+ });
@@ -0,0 +1,27 @@
1
+ import React, { memo } from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import type { PixelRect } from '../layout/LayoutCalculator';
4
+
5
+ type Props = {
6
+ rect: PixelRect;
7
+ children: React.ReactNode;
8
+ };
9
+
10
+ export const GridTile = memo(function GridTile({ rect, children }: Props) {
11
+ return (
12
+ <View
13
+ style={[
14
+ styles.tile,
15
+ { left: rect.x, top: rect.y, width: rect.width, height: rect.height },
16
+ ]}
17
+ >
18
+ {children}
19
+ </View>
20
+ );
21
+ });
22
+
23
+ const styles = StyleSheet.create({
24
+ tile: {
25
+ position: 'absolute',
26
+ },
27
+ });
@@ -0,0 +1,74 @@
1
+ import { useRef } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4
+ import Animated, { useAnimatedStyle, useSharedValue, runOnJS } from 'react-native-reanimated';
5
+ import type { PlacedTile, TileSize, GridConfig } from '../types';
6
+ import { columnWidth } from '../layout/LayoutCalculator';
7
+ import { useGridDrag } from '../context/GridDragContext';
8
+
9
+ type Props = {
10
+ tile: PlacedTile;
11
+ config: GridConfig;
12
+ containerWidth: number;
13
+ };
14
+
15
+ export function ResizeHandle({ tile, config, containerWidth }: Props) {
16
+ const { commitResize } = useGridDrag();
17
+ const initSize = useRef<TileSize>(tile.size);
18
+ const deltaW = useSharedValue(0);
19
+ const deltaH = useSharedValue(0);
20
+
21
+ const colW = columnWidth(config, containerWidth);
22
+
23
+ function onCommit(w: number, h: number) {
24
+ const minW = tile.minSize?.w ?? 1;
25
+ const minH = tile.minSize?.h ?? 1;
26
+ const maxW = tile.maxSize?.w ?? config.columns;
27
+ const maxH = tile.maxSize?.h ?? 99;
28
+ const newSize: TileSize = {
29
+ w: Math.max(minW, Math.min(maxW, w)),
30
+ h: Math.max(minH, Math.min(maxH, h)),
31
+ };
32
+ commitResize(tile, newSize);
33
+ }
34
+
35
+ const gesture = Gesture.Pan()
36
+ .onBegin(() => {
37
+ initSize.current = tile.size;
38
+ deltaW.value = 0;
39
+ deltaH.value = 0;
40
+ })
41
+ .onUpdate((e) => {
42
+ deltaW.value = Math.round(e.translationX / (colW + config.gap));
43
+ deltaH.value = Math.round(e.translationY / (config.rowHeight + config.gap));
44
+ })
45
+ .onEnd(() => {
46
+ const newW = initSize.current.w + deltaW.value;
47
+ const newH = initSize.current.h + deltaH.value;
48
+ deltaW.value = 0;
49
+ deltaH.value = 0;
50
+ runOnJS(onCommit)(newW, newH);
51
+ });
52
+
53
+ const animStyle = useAnimatedStyle(() => ({
54
+ transform: [{ translateX: deltaW.value * (colW + config.gap) }, { translateY: deltaH.value * (config.rowHeight + config.gap) }],
55
+ }));
56
+
57
+ return (
58
+ <GestureDetector gesture={gesture}>
59
+ <Animated.View style={[styles.handle, animStyle]} />
60
+ </GestureDetector>
61
+ );
62
+ }
63
+
64
+ const styles = StyleSheet.create({
65
+ handle: {
66
+ position: 'absolute',
67
+ bottom: 4,
68
+ right: 4,
69
+ width: 20,
70
+ height: 20,
71
+ borderRadius: 4,
72
+ backgroundColor: 'rgba(255,255,255,0.9)',
73
+ },
74
+ });