diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..578538c4e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing +Thank you for considering to contribute to Craft.js! + +From proofreading, translating, writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests, or writing code, there are many ways to contribute. Upon contribution as per the [all-contributors](https://allcontributors.org/) specification, your profile will be recognised in our README's contributors section. + +To get started right away, have a look at our [project tracker](https://github.com/prevwong/craft.js/projects) to check out a list of things that we'd like to work on right now. + +If you are interested in proposing a new feature or have found a bug that you'd like to fix, please file a new [issue](https://github.com/prevwong/craft.js/issues). + + +# Setup +1. Fork this repository and create your branch from `master` +```bash +git clone https://github.com/your-name/craft.js +cd craft.js +``` + +2. Install the dependencies and start the development server +```bash +> yarn install +> yarn dev +``` + +3. Here are some additional npm scripts that might be useful +```bash +> yarn clean # clean all build files from all packages in the monorepo +> yarn build # create production build for all craftjs packages +> yarn lint # run tests across the monorepo +``` +4. Submit a [pull request](https://github.com/prevwong/craft.js/compare) to merge the changes from your fork :heart: + +# License +By contributing, you agree that your contributions will be licensed under MIT License. + diff --git a/packages/core/README.md b/packages/core/README.md index 4492fdfd9..d2f297129 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -9,7 +9,7 @@
- +

diff --git a/packages/docs/docs/dev.md b/packages/docs/docs/dev.md new file mode 100644 index 000000000..956d8d711 --- /dev/null +++ b/packages/docs/docs/dev.md @@ -0,0 +1,263 @@ +--- +id: dev +title: Craft.js - Build any page editor with React +--- + + + +You know a web application is fancy when it has a page editor. Without a doubt, a page editor helps boost user experience significantly. If you're a frontend developer and have been tasked to be build one before, then you know precisely the difficulties and challenges of building one. + +Existing libraries such as Grape.js or react-page are great for a working out-of-the-box page editor solution. However, as soon as you need to customise the look and feel of the page editor itself, you will find yourself hacking in the library's code. + + +## Introducing Craft.js +Craft.js is a React framework to build any type of page editor. Instead of providing a working page editor implementation with a user interface, Craft.js provides an abstraction for you to implement your own page editor upon. It comes backed-in with an extensible drag-n-drop system which handles the way React elements should be rendered/updated, and a cohesive API to interact with the editor which you can additionally implement your own features on top of. + +### TL;DR +- Design your own user interface for your page editor +- Write React components that end-user could edit +- Govern drag-and-drop conditions for your components +- Control how your components should be edited. From simple text fields to content editables and drag to resize; if you can do it in React, then you can do it with Craft.js. + + +## Editable React Components +Let's start with a simple Card component like this: + +```jsx +const Card = ({title}) => { + return ( +

+

{title}

+
+

Hi

+
+
+ ) +} +``` + +First, to integrate it with with Craft.js' drag-and-drop system, we just need to do the following modifications: + +```jsx +import {useNode} from "@craftjs/core"; + +const Card = ({title}) => { + const { connectors: { connect, drag } } = useNode(); + return ( +
connect(drag(dom))}> +

{title}

+
+

Hi

+
+
+ ) +} +``` + +What's happening here? +- We passed the `connect` connector to the root element of our component; this tells Craft.js that this element represents the `Card` component. Hence, the dimensions of the specified element will be taken into consideration during drag and drop events. +- Then, we also passed `drag` connector to the same root element; this adds the drag handlers to the DOM. If the component is rendered as the child of a ``, the user will be able to drag this element and it will move the entire Text component. + + +Next, we might want to be able to control the drag-n-drop rules of our Card component. For example, let's say we only want our Card component to be draggable as long as its `title` prop is not "No Drag". We can achieve this easily as such: +```jsx +const Card = () => {...} + +Card.craft = { + rules: { + canDrag: (node) => node.data.props.title != "No Drag" + } +} +``` + +#### Droppable regions +Next, let's take a look at the `#p-only` element we specified in our Card component. What if we want this area to be droppable where only `p` can be dropped? + +This is where the`` component provided by Craft.js becomes useful. It defines a droppable region where each of its immediate child is draggable. + + +```jsx +import {useNode, Canvas} from "@craftjs/core"; + +const Card = ({title}) => { + const { connectors: { connect, drag } } = useNode(); + return ( +
connect(drag(dom))}> +

{title}

+ +

Hi

+
+
+ ) +} +``` +Your next question might be about how we control our newly created droppable region. The `` component accepts an `is` prop which can be either a HTML element or a React component (by default, it's a `div`). So, if we supply a React component, we can essentially achieve the same design flexibility as we have with our Card component: + +```jsx +import {useNode, Canvas} from "@craftjs/core"; + +const Container = ({children}) => { + const { connectors: {connect} } = useNode(); + return ( +
connect(dom)}> + {children} +
+ ) +} + +Container.craft = { + rules: { + canMoveIn: (incomingNode) => incomingNode.data.type == "p" + } +} + +const Card = ({title}) => { + const { connectors: { connect, drag } } = useNode(); + return ( +
connect(drag(dom))}> +

{title}

+ +

Hi

+
+
+ ) +} +``` + +Let's break this down a bit. Our `` component is being rendered as a droppable region. This means, all dropped elements will be rendered in the component's `children` prop. Next, we also specified the `connectors` just as we did previously. This time, we specified a `canMoveIn` rule where only elements with the `p` tag will be accepted into the component. + +## Control editing behaviours +Of course, any page editor must allow the end-user to edit the elements that are rendered. With Craft.js, you are in control of this as well. + +Let's make our `h2` element content editable so our users could visually edit the `title` prop: + +```jsx +const Card = ({title}) => { + const { connectors: { connect, drag }, setProp } = useNode(); + return ( +
connect(drag(dom))}> +

setProp(e.target.innerText)}>{title}

+ ... +
+ ) +} +``` +> In a real application, you may want to consider using react-contenteditable + +Obviously, we do not want the element to be content editable all the time; perhaps only when our Card component is actually clicked: + +```jsx +const Card = ({title}) => { + const { connectors: { connect, drag }, setProp, isSelected } = useNode((node) => ({ + isSelected: node.events.selected + })); + + return ( +
connect(drag(dom))}> +

setProp(e.target.innerText)}>{title}

+ ... +
+ ) +} +``` +What we are doing is essentially accessing Craft's internal state and retrieving information about the current instance of our Card component. In this case, we are retrieving the `selected` event state which returns `true` when the user clicks on the DOM specified by the `connect` connector of the Component. + +## Your page editor, your user interface + +Essentially, Craft.js exposes these few React components: +- `Editor` creates the Editor context and sets everything up +- `Frame` defines the editable area + +As you may have noticed, none of these are UI components. Instead, they are intended to be plugged into any user interface you would like to implement for your page editor. + +Let's take a look at a super simple interface that we could design: + +```jsx +// pages/index.js +import React from 'react'; + +export default function App() { + return ( + <> +

My page editor

+ {/* sets everything up */ } +
+ {/* This part is now editable */ } + { /* Creates a gray droppable div */ } +

Drag me around

+

I'm draggable too + {/* a div that is both droppable and draggable */} +

Hi

{/* a draggable h2 */} +
+ + +
+
+ + ); +} +``` + +### Interacting with the editor +Your user interface will most likely need to display some information or perform certain editor-related actions. + +Let's say we want to design a Toolbar component that does the following things: + - Tells us the the type of the currently selected element + - Has a button to save the current editor state + - Has another button to load the last saved editor state + +```jsx +import React, {useState} from 'react'; + +export default function App() { + return ( + <> +

My page editor

+ + {/* Add this */} +
+ + ... + +
+
+ + ); +} + +const Toolbar = () => { + const [savedState, setSavedState] = useState(); + const { selectedType, actions, query } = useEditor(state => { + const selectedId = state.events.selected; + return { + selectedType: selectedId && state.nodes[selectedId].data.type + } + }); + + return ( +
+

Currently selected: {selectedType}

+ { + const editorState = query.serialize(); + setSavedState(editorState); + }}>Save checkpoint + { + savedState ? ( + + actions.deserialize(editorState) + }>Load from checkpoint + ) : null + } +
+ ) +} +``` + +Let's break this down: +- First, we access the editor's state to retrieve the type of the currently selected element +- Then, when the "Save Checkpoint" button is clicked, we use the `serialize` query which tells the editor to return its state in a serialised JSON form. We then save the JSON output in our component's state +- Once we have the JSON, we display the "Load from checkpoint" button. When this button is clicked, we simply call the `deserialize` editor action which essentially returns the editor to the state stored in the JSON output. + + +## Closing words +This has been a high-level overview of Craft.js and we've only covered some very basic examples. We've seen how we could easily control almost every aspect of the page editor experience. Hopefully, this article has given you an idea on the possibilities of what you can do with Craft.js. diff --git a/packages/docs/docusaurus.config.js b/packages/docs/docusaurus.config.js index ce2125b59..9bbbd4ec0 100755 --- a/packages/docs/docusaurus.config.js +++ b/packages/docs/docusaurus.config.js @@ -44,7 +44,7 @@ module.exports = { }, { label: 'Tutorial', - to: 'docs/basic-tutorial', + to: 'docs/guides/basic-tutorial', }, { label: 'API Reference',