diff --git a/demos/src/Nodes/TaskItem/React/index.html b/demos/src/Nodes/TaskItem/React/index.html
new file mode 100644
index 00000000..fe80515c
--- /dev/null
+++ b/demos/src/Nodes/TaskItem/React/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/src/Nodes/TaskItem/React/index.jsx b/demos/src/Nodes/TaskItem/React/index.jsx
new file mode 100644
index 00000000..a1443e36
--- /dev/null
+++ b/demos/src/Nodes/TaskItem/React/index.jsx
@@ -0,0 +1,63 @@
+import React from 'react'
+import { useEditor, EditorContent } from '@tiptap/react'
+import Document from '@tiptap/extension-document'
+import Paragraph from '@tiptap/extension-paragraph'
+import Text from '@tiptap/extension-text'
+import TaskList from '@tiptap/extension-task-list'
+import TaskItem from '@tiptap/extension-task-item'
+import './styles.scss'
+
+export default () => {
+ const editor = useEditor({
+ extensions: [
+ Document,
+ Paragraph,
+ Text,
+ TaskList,
+ TaskItem.configure({
+ nested: true,
+ }),
+ ],
+ content: `
+
+ - A list item
+ - And another one
+
+ `,
+ })
+
+ if (!editor) {
+ return null
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/demos/src/Nodes/TaskItem/React/index.spec.js b/demos/src/Nodes/TaskItem/React/index.spec.js
new file mode 100644
index 00000000..a8da084e
--- /dev/null
+++ b/demos/src/Nodes/TaskItem/React/index.spec.js
@@ -0,0 +1,7 @@
+context('/src/Nodes/TaskItem/React/', () => {
+ before(() => {
+ cy.visit('/src/Nodes/TaskItem/React/')
+ })
+
+ // TODO: Write tests
+})
diff --git a/demos/src/Nodes/TaskItem/React/styles.scss b/demos/src/Nodes/TaskItem/React/styles.scss
new file mode 100644
index 00000000..2a3190c0
--- /dev/null
+++ b/demos/src/Nodes/TaskItem/React/styles.scss
@@ -0,0 +1,22 @@
+ul[data-type="taskList"] {
+ list-style: none;
+ padding: 0;
+
+ p {
+ margin: 0;
+ }
+
+ li {
+ display: flex;
+
+ > label {
+ flex: 0 0 auto;
+ margin-right: 0.5rem;
+ user-select: none;
+ }
+
+ > div {
+ flex: 1 1 auto;
+ }
+ }
+}
diff --git a/demos/src/Nodes/TaskList/React/index.html b/demos/src/Nodes/TaskList/React/index.html
new file mode 100644
index 00000000..10a1ad79
--- /dev/null
+++ b/demos/src/Nodes/TaskList/React/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/src/Nodes/TaskList/React/index.jsx b/demos/src/Nodes/TaskList/React/index.jsx
new file mode 100644
index 00000000..a1443e36
--- /dev/null
+++ b/demos/src/Nodes/TaskList/React/index.jsx
@@ -0,0 +1,63 @@
+import React from 'react'
+import { useEditor, EditorContent } from '@tiptap/react'
+import Document from '@tiptap/extension-document'
+import Paragraph from '@tiptap/extension-paragraph'
+import Text from '@tiptap/extension-text'
+import TaskList from '@tiptap/extension-task-list'
+import TaskItem from '@tiptap/extension-task-item'
+import './styles.scss'
+
+export default () => {
+ const editor = useEditor({
+ extensions: [
+ Document,
+ Paragraph,
+ Text,
+ TaskList,
+ TaskItem.configure({
+ nested: true,
+ }),
+ ],
+ content: `
+
+ - A list item
+ - And another one
+
+ `,
+ })
+
+ if (!editor) {
+ return null
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/demos/src/Nodes/TaskList/React/index.spec.js b/demos/src/Nodes/TaskList/React/index.spec.js
new file mode 100644
index 00000000..f9dab673
--- /dev/null
+++ b/demos/src/Nodes/TaskList/React/index.spec.js
@@ -0,0 +1,113 @@
+context('/src/Nodes/TaskList/React/', () => {
+ before(() => {
+ cy.visit('/src/Nodes/TaskList/React/')
+ })
+
+ beforeEach(() => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.setContent('Example Text
')
+ cy.get('.ProseMirror').type('{selectall}')
+ })
+ })
+
+ it('should parse unordered lists correctly', () => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.setContent(
+ '',
+ )
+ expect(editor.getHTML()).to.eq(
+ '',
+ )
+ })
+ })
+
+ it('should parse unordered lists without paragraphs correctly', () => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.setContent(
+ '',
+ )
+ expect(editor.getHTML()).to.eq(
+ '',
+ )
+ })
+ })
+
+ it('the button should make the selected line a task list item', () => {
+ cy.get('.ProseMirror ul').should('not.exist')
+
+ cy.get('.ProseMirror ul li').should('not.exist')
+
+ cy.get('button:nth-child(1)').click()
+
+ cy.get('.ProseMirror').find('ul[data-type="taskList"]').should('contain', 'Example Text')
+
+ cy.get('.ProseMirror').find('ul[data-type="taskList"] li').should('contain', 'Example Text')
+ })
+
+ it('the button should toggle the task list', () => {
+ cy.get('.ProseMirror ul').should('not.exist')
+
+ cy.get('button:nth-child(1)').click()
+
+ cy.get('.ProseMirror').find('ul[data-type="taskList"]').should('contain', 'Example Text')
+
+ cy.get('button:nth-child(1)').click()
+
+ cy.get('.ProseMirror ul').should('not.exist')
+ })
+
+ it('should make the paragraph a task list when the keyboard shortcut is pressed', () => {
+ cy.get('.ProseMirror')
+ .trigger('keydown', { modKey: true, shiftKey: true, key: '9' })
+ .find('ul li')
+ .should('contain', 'Example Text')
+ })
+
+ it('should leave the list with double enter', () => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.clearContent()
+ })
+
+ cy.get('.ProseMirror').type('[ ] List Item 1{enter}{enter}Paragraph')
+
+ cy.get('.ProseMirror').find('li').its('length').should('eq', 1)
+
+ cy.get('.ProseMirror').find('p').should('contain', 'Paragraph')
+ })
+
+ it('should make a task list from square brackets', () => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.clearContent()
+ })
+
+ cy.get('.ProseMirror').type('[ ] List Item 1{enter}List Item 2')
+
+ cy.get('.ProseMirror')
+ .find('li:nth-child(1)')
+ .should('contain', 'List Item 1')
+ .should('have.attr', 'data-checked', 'false')
+
+ cy.get('.ProseMirror')
+ .find('li:nth-child(2)')
+ .should('contain', 'List Item 2')
+ .should('have.attr', 'data-checked', 'false')
+ })
+
+ it('should make a task list from checked square brackets', () => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.clearContent()
+ })
+
+ cy.get('.ProseMirror').type('[x] List Item 1{enter}List Item 2')
+
+ cy.get('.ProseMirror')
+ .find('li:nth-child(1)')
+ .should('contain', 'List Item 1')
+ .should('have.attr', 'data-checked', 'true')
+
+ cy.get('.ProseMirror')
+ .find('li:nth-child(2)')
+ .should('contain', 'List Item 2')
+ .should('have.attr', 'data-checked', 'false')
+ })
+})
diff --git a/demos/src/Nodes/TaskList/React/styles.scss b/demos/src/Nodes/TaskList/React/styles.scss
new file mode 100644
index 00000000..2a3190c0
--- /dev/null
+++ b/demos/src/Nodes/TaskList/React/styles.scss
@@ -0,0 +1,22 @@
+ul[data-type="taskList"] {
+ list-style: none;
+ padding: 0;
+
+ p {
+ margin: 0;
+ }
+
+ li {
+ display: flex;
+
+ > label {
+ flex: 0 0 auto;
+ margin-right: 0.5rem;
+ user-select: none;
+ }
+
+ > div {
+ flex: 1 1 auto;
+ }
+ }
+}