diff --git a/docs/docs/widgets/kanban.md b/docs/docs/widgets/kanban.md
new file mode 100644
index 0000000000..a3b56fd173
--- /dev/null
+++ b/docs/docs/widgets/kanban.md
@@ -0,0 +1,100 @@
+---
+id: kanban
+title: Kanban
+---
+
+# Kanban
+
+Kanban widget allows you to visually organize and prioritize your tasks with a transparent workflow. You can set the number of columns to display, enable/disable the add cards button, and bind data to the cards.
+
+
+
+## Events
+
+To add an event, click on the widget handle to open the widget properties on the right sidebar. Go to the **Events** section and click on **Add handler**.
+
+- [Card added](#card-added)
+- [Card removed](#card-removed)
+- [Card moved](#card-moved)
+- [Card selected](#card-selected)
+- [Card updated](#card-updated)
+
+Just like any other event on ToolJet, you can set multiple handlers for any of the above mentioned events.
+
+
+
+:::caution
+Please keep in mind that you need to provide an `id` for each card in the `Card data` field
+and this `id` must be of type string.
+:::
+
+
+
+
+
+| Properties | description | Expected value |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Columns | Enter the columns data - `id` and `title` in the form of array of objects or from a query that returns an array of objects. | `{{[{ "id": "1", "title": "to do" },{ "id": "2", "title": "in progress" },{ "id": "2", "title": "Completed" }]}}` or `{{queries.xyz.data}}` |
+| Card data | Enter the cards data - `id`, `title` and `columnId` in the form of array of objects or from a query that returns an array of objects. | `{{[{ id: "01", title: "one", columnId: "1" },{ id: "02", title: "two", columnId: "1" },{ id: "03", title: "three", columnId: "2" }]}}` or `{{queries.abc.data}}` |
+| Enable Add Card | This property allows you to show or hide the `Add Cards` button at the bottom of every column. | By deafult its enabled, you can programmatically set `{{true}}` or `{{false}}` enable/disable button by clicking on the `Fx` next to it |
+
+## General
+
+Tooltip: Set a tooltip text to specify the information about the data/kanban when the user moves the mouse pointer over the widget.
+
+## Layout
+
+
+
+| Layout | description | Expected value |
+| --------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
+| Show on desktop | Toggle on or off to display the widget in desktop view. | You can programmatically set the value by clicking on `Fx` to set the value `{{true}}` or `{{false}}` |
+| Show on mobile | Toggle on or off to display the widget in mobile view. | You can programmatically set the value by clicking on `Fx` to set the value `{{true}}` or `{{false}}` |
+
+## Styles
+
+
+
+
+
+
+
+| Style | Description |
+| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Disable | If disabled or set to `{{false}}` the widget will be locked and becomes non-functional. By default, its disabled i.e. its value is set to `{{true}}` . |
+| Visibility | This is to control the visibility of the widget. If `{{false}}`/disabled the widget will not visible after the app is deployed. By default, it's enabled (set to `{{true}}`). |
+| Width | This property sets the width of the column. |
+| Accent color | You can change the accent color of the column title by entering the Hex color code or choosing a color of your choice from the color picker. |
+
+## Exposed variables
+
+
+
+
+
+
+
+| Variable | Description |
+| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| columns | The `columns` variable is an array of objects that includes the columns data in the respective objects. Since the columns variable is an array you'll need to specify the index of the object in the array to get the data within that object. Each object within a column has two keys - `id` and `title` and an array `cards` which is again an array of objects. Example: If you want to get the title of second card then you'll use `{{components.kanbanboard1.columns[1].title}}` - here we have specified the array index as `[1]` and then key which is the `title`. Similary you can get the card details using `{{components.kanbanboard1.columns[0].cards[1].title}}` |
+| lastAddedCard | The variable `lastAddedCard` holds the properties of the card that has been added lastly. It holds the following data - `id`, `title`, and `columnId` of the last addded card. You can get the values using `{{components.kanbanboard1.lastAddedCard.title}}` |
+| lastRemovedCard | The variable `lastRemovedCard` holds the properties of the card that has been recently deleted from the kanban. It holds the following data - `id`, `title`, and `columnId` of the recently deleted card. You can get the values using `{{components.kanbanboard1.lastRemovedCard.title}}` |
+| lastCardMovement | The variable `lastCardMovement` holds the properties of the card that has been recently moved from its original position. It holds the following data - `originColumnId`, `destinationColumnId`, `originCardIndex`, `destinationCardIndex` and an object `cardDetails` which includes `title`. You can get the values using `{{components.kanbanboard1.lastCardMovement.cardDetails.title}}` or `{{components.kanbanboard1.lastCardMovement.destinationCardIndex}}` |
+| lastUpdatedCard | The variable `lastUpdatedCard` holds `id`, `title`, and `columnId` of the latest modified card. You can get the values using `{{components.kanbanboard1.lastUpdatedCard.columnId}}` |
+| selectedCard | The variable `selectedCard` holds `id`, `title`, `columnId`, and `description` of the selected card in the kanban. You can get the values using `{{components.kanbanboard1.selectedCard.description}}` |
diff --git a/docs/sidebars.js b/docs/sidebars.js
index fa0bbec2c1..0a13bac335 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -115,6 +115,7 @@ const sidebars = {
'widgets/file-picker',
'widgets/iframe',
'widgets/image',
+ 'widgets/kanban',
'widgets/listview',
'widgets/map',
'widgets/modal',
diff --git a/docs/static/img/widgets/kanban/kanban-events.png b/docs/static/img/widgets/kanban/kanban-events.png
new file mode 100644
index 0000000000..37f96ad0f8
Binary files /dev/null and b/docs/static/img/widgets/kanban/kanban-events.png differ
diff --git a/docs/static/img/widgets/kanban/kanban.png b/docs/static/img/widgets/kanban/kanban.png
new file mode 100644
index 0000000000..00d7be5f29
Binary files /dev/null and b/docs/static/img/widgets/kanban/kanban.png differ
diff --git a/docs/static/img/widgets/kanban/layout.png b/docs/static/img/widgets/kanban/layout.png
new file mode 100644
index 0000000000..c16fcd0450
Binary files /dev/null and b/docs/static/img/widgets/kanban/layout.png differ
diff --git a/docs/static/img/widgets/kanban/properties.png b/docs/static/img/widgets/kanban/properties.png
new file mode 100644
index 0000000000..1288ab5541
Binary files /dev/null and b/docs/static/img/widgets/kanban/properties.png differ
diff --git a/docs/static/img/widgets/kanban/styles.png b/docs/static/img/widgets/kanban/styles.png
new file mode 100644
index 0000000000..01c5ffced2
Binary files /dev/null and b/docs/static/img/widgets/kanban/styles.png differ
diff --git a/docs/static/img/widgets/kanban/variables.png b/docs/static/img/widgets/kanban/variables.png
new file mode 100644
index 0000000000..eefe9b47ed
Binary files /dev/null and b/docs/static/img/widgets/kanban/variables.png differ
diff --git a/frontend/assets/images/icons/editor/edit.svg b/frontend/assets/images/icons/editor/edit.svg
new file mode 100644
index 0000000000..8a90d97d5b
--- /dev/null
+++ b/frontend/assets/images/icons/editor/edit.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/assets/images/icons/widgets/kanbanboard.svg b/frontend/assets/images/icons/widgets/kanbanboard.svg
new file mode 100644
index 0000000000..b826e73a7a
--- /dev/null
+++ b/frontend/assets/images/icons/widgets/kanbanboard.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 4efa2f30a7..87712f01e2 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -52,6 +52,7 @@
"query-string": "^6.13.6",
"rc-slider": "^9.7.5",
"react": "^16.14.0",
+ "react-beautiful-dnd": "^13.1.0",
"react-big-calendar": "^0.38.0",
"react-bootstrap": "^1.5.2",
"react-circular-progressbar": "^2.0.4",
@@ -17237,6 +17238,15 @@
"@types/node": "*"
}
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/html-minifier-terser": {
"version": "5.1.2",
"integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w=="
@@ -17337,6 +17347,17 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-redux": {
+ "version": "7.1.24",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
+ "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
"node_modules/@types/react-transition-group": {
"version": "4.4.1",
"integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==",
@@ -19386,6 +19407,14 @@
"urix": "^0.1.0"
}
},
+ "node_modules/css-box-model": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
+ "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
+ "dependencies": {
+ "tiny-invariant": "^1.0.6"
+ }
+ },
"node_modules/css-loader": {
"version": "6.5.1",
"integrity": "sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==",
@@ -28359,6 +28388,11 @@
"performance-now": "^2.1.0"
}
},
+ "node_modules/raf-schd": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
+ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
+ },
"node_modules/randombytes": {
"version": "2.1.0",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
@@ -28519,6 +28553,24 @@
"pure-color": "^1.2.0"
}
},
+ "node_modules/react-beautiful-dnd": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
+ "integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2",
+ "css-box-model": "^1.2.0",
+ "memoize-one": "^5.1.1",
+ "raf-schd": "^4.0.2",
+ "react-redux": "^7.2.0",
+ "redux": "^4.0.4",
+ "use-memo-one": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.5 || ^17.0.0",
+ "react-dom": "^16.8.5 || ^17.0.0"
+ }
+ },
"node_modules/react-big-calendar": {
"version": "0.38.0",
"integrity": "sha512-eoVkt9gTo+f1HBL09+o7dYLxp6QxHv52fcn50P5PfaWp3S98uGLQqoqsvghT85koMKvGfDVa5V0+J7yHcaF07Q==",
@@ -29079,6 +29131,46 @@
"react-dom": "~16"
}
},
+ "node_modules/react-redux": {
+ "version": "7.2.8",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
+ "integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==",
+ "dependencies": {
+ "@babel/runtime": "^7.15.4",
+ "@types/react-redux": "^7.1.20",
+ "hoist-non-react-statics": "^3.3.2",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2"
+ },
+ "peerDependencies": {
+ "react": "^16.8.3 || ^17 || ^18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux/node_modules/@babel/runtime": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
+ "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
+ "dependencies": {
+ "regenerator-runtime": "^0.13.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/react-redux/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+ },
"node_modules/react-rnd": {
"version": "10.3.0",
"integrity": "sha512-v+0TRPIaRWY25TYv02vLQHYpACbkX+4xKvsyIrUEy4bMpq0bP1oEiaxTorp0Xn72IVv0QZV1vOnZimgTEB/skw==",
@@ -31033,6 +31125,14 @@
}
}
},
+ "node_modules/use-memo-one": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
+ "integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0"
+ }
+ },
"node_modules/util": {
"version": "0.10.3",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
@@ -45089,6 +45189,15 @@
"@types/node": "*"
}
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"@types/html-minifier-terser": {
"version": "5.1.2",
"integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w=="
@@ -45189,6 +45298,17 @@
"@types/react": "*"
}
},
+ "@types/react-redux": {
+ "version": "7.1.24",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
+ "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
+ "requires": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
"@types/react-transition-group": {
"version": "4.4.1",
"integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==",
@@ -46747,6 +46867,14 @@
"urix": "^0.1.0"
}
},
+ "css-box-model": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
+ "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
+ "requires": {
+ "tiny-invariant": "^1.0.6"
+ }
+ },
"css-loader": {
"version": "6.5.1",
"integrity": "sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==",
@@ -53354,6 +53482,11 @@
"performance-now": "^2.1.0"
}
},
+ "raf-schd": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
+ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
+ },
"randombytes": {
"version": "2.1.0",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
@@ -53470,6 +53603,20 @@
"pure-color": "^1.2.0"
}
},
+ "react-beautiful-dnd": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
+ "integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
+ "requires": {
+ "@babel/runtime": "^7.9.2",
+ "css-box-model": "^1.2.0",
+ "memoize-one": "^5.1.1",
+ "raf-schd": "^4.0.2",
+ "react-redux": "^7.2.0",
+ "redux": "^4.0.4",
+ "use-memo-one": "^1.1.1"
+ }
+ },
"react-big-calendar": {
"version": "0.38.0",
"integrity": "sha512-eoVkt9gTo+f1HBL09+o7dYLxp6QxHv52fcn50P5PfaWp3S98uGLQqoqsvghT85koMKvGfDVa5V0+J7yHcaF07Q==",
@@ -53884,6 +54031,34 @@
"webrtc-adapter": "^7.2.1"
}
},
+ "react-redux": {
+ "version": "7.2.8",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
+ "integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==",
+ "requires": {
+ "@babel/runtime": "^7.15.4",
+ "@types/react-redux": "^7.1.20",
+ "hoist-non-react-statics": "^3.3.2",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.17.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
+ "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+ }
+ }
+ },
"react-rnd": {
"version": "10.3.0",
"integrity": "sha512-v+0TRPIaRWY25TYv02vLQHYpACbkX+4xKvsyIrUEy4bMpq0bP1oEiaxTorp0Xn72IVv0QZV1vOnZimgTEB/skw==",
@@ -55350,6 +55525,12 @@
"use-isomorphic-layout-effect": "^1.0.0"
}
},
+ "use-memo-one": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
+ "integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
+ "requires": {}
+ },
"util": {
"version": "0.10.3",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
diff --git a/frontend/package.json b/frontend/package.json
index 01b39179d1..0b111ce953 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -48,6 +48,7 @@
"query-string": "^6.13.6",
"rc-slider": "^9.7.5",
"react": "^16.14.0",
+ "react-beautiful-dnd": "^13.1.0",
"react-big-calendar": "^0.38.0",
"react-bootstrap": "^1.5.2",
"react-circular-progressbar": "^2.0.4",
diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx
index 957398c9a3..5b9a9d6474 100644
--- a/frontend/src/Editor/Box.jsx
+++ b/frontend/src/Editor/Box.jsx
@@ -42,6 +42,7 @@ import { ButtonGroup } from './Components/ButtonGroup';
import { CustomComponent } from './Components/CustomComponent/CustomComponent';
import { VerticalDivider } from './Components/verticalDivider';
import { PDF } from './Components/PDF';
+import { KanbanBoard } from './Components/KanbanBoard/KanbanBoard';
import { Steps } from './Components/Steps';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import '@/_styles/custom.scss';
@@ -91,6 +92,7 @@ const AllComponents = {
CustomComponent,
VerticalDivider,
PDF,
+ KanbanBoard,
Steps,
};
diff --git a/frontend/src/Editor/Components/KanbanBoard/Board.jsx b/frontend/src/Editor/Components/KanbanBoard/Board.jsx
new file mode 100644
index 0000000000..e1557cf194
--- /dev/null
+++ b/frontend/src/Editor/Components/KanbanBoard/Board.jsx
@@ -0,0 +1,126 @@
+import React from 'react';
+import { DragDropContext } from 'react-beautiful-dnd';
+import { v4 as uuidv4 } from 'uuid';
+import Column from './Column';
+import { reorderCards, moveCards } from './utils';
+
+const grid = 8;
+
+const getItemStyle = (isDragging, draggableStyle) => {
+ const _draggableStyle = isDragging
+ ? { ...draggableStyle, left: draggableStyle.left - 100, top: draggableStyle.top - 100 }
+ : draggableStyle;
+
+ return {
+ ..._draggableStyle,
+ userSelect: 'none',
+ padding: grid * 2,
+ margin: `0 0 ${grid}px 0`,
+ background: isDragging ? '#c2cfff' : '#fefefe',
+ };
+};
+
+function Board({ height, state, colStyles, setState, fireEvent, setExposedVariable }) {
+ const addNewItem = (state, keyIndex) => {
+ const newItem = {
+ id: uuidv4(),
+ title: 'New card',
+ columnId: state[keyIndex].id,
+ };
+ const newState = [...state];
+ if (!newState[keyIndex]['cards']) [(newState[keyIndex]['cards'] = [])];
+ newState[keyIndex]['cards'].push(newItem);
+ setState(newState);
+ setExposedVariable('lastAddedCard', newItem).then(() => fireEvent('onCardAdded'));
+ };
+
+ function onDragEnd(result) {
+ const { source, destination } = result;
+
+ // dropped outside the list
+ if (destination && destination !== null) {
+ const sInd = +source.droppableId;
+ const dInd = +destination.droppableId;
+ const originColumnId = state[sInd].id;
+ const destinationColumnId = state[dInd].id;
+
+ const card = state[sInd]['cards'][source.index];
+ const cardDetails = {
+ title: card.title,
+ };
+
+ if (sInd === dInd) {
+ const items = reorderCards(state[sInd]['cards'], source.index, destination.index);
+ const newState = [...state];
+ newState[sInd]['cards'] = items;
+ setState(newState);
+ } else {
+ const result = moveCards(state[sInd]['cards'], state[dInd].cards, source, destination);
+ const newState = [...state];
+ newState[sInd]['cards'] = result[sInd];
+ newState[dInd]['cards'] = result[dInd];
+ newState[dInd]['cards'][destination.index].columnId = newState[dInd].id;
+
+ setState(newState);
+ }
+
+ const movementDetails = {
+ originColumnId,
+ destinationColumnId,
+ originCardIndex: sInd,
+ destinationCardIndex: dInd,
+ cardDetails,
+ };
+ setExposedVariable('lastCardMovement', movementDetails).then(() => fireEvent('onCardMoved'));
+ }
+ }
+
+ const getListStyle = (isDraggingOver) => ({
+ ...colStyles,
+ padding: grid,
+ borderColor: isDraggingOver && '#c0ccf8',
+ });
+
+ const updateCardProperty = (columnIndex, cardIndex, property, newValue) => {
+ const columnOfCardToBeUpdated = state[columnIndex];
+ const cardSetOfTheCardToBeUpdated = columnOfCardToBeUpdated.cards;
+ const cardToBeUpdated = cardSetOfTheCardToBeUpdated[cardIndex];
+ const updatedCard = { ...cardToBeUpdated, [property]: newValue };
+ const updatedCardSet = cardSetOfTheCardToBeUpdated.map((card, index) => (index === cardIndex ? updatedCard : card));
+ const updatedColumn = { ...columnOfCardToBeUpdated, cards: updatedCardSet };
+ const newState = state.map((column, index) => (index === columnIndex ? updatedColumn : column));
+ setState(newState);
+
+ setExposedVariable('lastUpdatedCard', updatedCard).then(() => fireEvent('onCardUpdated'));
+ };
+
+ return (
+