@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "react-flow-examples",
|
||||
"name": "react-flow-tests",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -4,12 +4,10 @@
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<meta name="theme-color" content="#222222" />
|
||||
<meta name="description" content="react flow examples" />
|
||||
<title>React Flow Examples</title>
|
||||
<meta name="description" content="React Flow test flows" />
|
||||
<title>React Flow - Test Flows</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, { removeElements, addEdge } from 'react-flow-renderer';
|
||||
|
||||
const onLoad = (reactFlowInstance) => reactFlowInstance.fitView();
|
||||
|
||||
const onNodeMouseEnter = (event, node) => console.log('mouse enter:', node);
|
||||
const onNodeMouseMove = (event, node) => console.log('mouse move:', node);
|
||||
const onNodeMouseLeave = (event, node) => console.log('mouse leave:', node);
|
||||
const onNodeContextMenu = (event, node) => {
|
||||
event.preventDefault();
|
||||
console.log('context menu:', node);
|
||||
};
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: '1',
|
||||
sourcePosition: 'right',
|
||||
type: 'input',
|
||||
className: 'dark-node',
|
||||
data: { label: 'Input' },
|
||||
position: { x: 0, y: 80 },
|
||||
},
|
||||
{ id: '2', sourcePosition: 'right', targetPosition: 'left', data: { label: 'A Node' }, position: { x: 250, y: 0 } },
|
||||
{ id: '3', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 3' }, position: { x: 250, y: 160 } },
|
||||
{ id: '4', sourcePosition: 'right', targetPosition: 'left', data: { label: 'Node 4' }, position: { x: 500, y: 0 } },
|
||||
{ id: '5', sourcePosition: 'top', targetPosition: 'bottom', data: { label: 'Node 5' }, position: { x: 500, y: 100 } },
|
||||
{ id: '6', sourcePosition: 'bottom', targetPosition: 'top', data: { label: 'Node 6' }, position: { x: 500, y: 230 } },
|
||||
{
|
||||
id: '7',
|
||||
type: 'output',
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
data: { label: 'Node 7' },
|
||||
position: { x: 750, y: 50 },
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
type: 'output',
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
data: { label: 'Node 8' },
|
||||
position: { x: 750, y: 300 },
|
||||
},
|
||||
|
||||
{ id: 'e1-2', source: '1', type: 'smoothstep', target: '2', animated: true },
|
||||
{ id: 'e1-3', source: '1', type: 'smoothstep', target: '3', animated: true },
|
||||
{ id: 'e1-4', source: '2', type: 'smoothstep', target: '4', label: 'edge label' },
|
||||
{ id: 'e3-5', source: '3', type: 'smoothstep', target: '5', animated: true },
|
||||
{ id: 'e3-6', source: '3', type: 'smoothstep', target: '6', animated: true },
|
||||
{ id: 'e5-7', source: '5', type: 'smoothstep', target: '7', animated: true },
|
||||
{ id: 'e6-8', source: '6', type: 'smoothstep', target: '8', animated: true },
|
||||
];
|
||||
|
||||
const HorizontalFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
const changeClassName = () => {
|
||||
setElements((elms) =>
|
||||
elms.map((el) => {
|
||||
if (el.type === 'input') {
|
||||
el.className = el.className ? '' : 'dark-node';
|
||||
}
|
||||
|
||||
return { ...el };
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
onLoad={onLoad}
|
||||
selectNodesOnDrag={false}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
onNodeMouseMove={onNodeMouseMove}
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
>
|
||||
<button onClick={changeClassName} style={{ position: 'absolute', right: 10, top: 30, zIndex: 4 }}>
|
||||
change class name
|
||||
</button>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalFlow;
|
||||
@@ -1,7 +1,5 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-family: sans-serif;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter as Router, Route, Switch, NavLink, withRouter } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Route, Switch, NavLink } from 'react-router-dom';
|
||||
|
||||
import Overview from './Overview';
|
||||
import Basic from './Basic';
|
||||
@@ -10,7 +10,6 @@ import Interaction from './Interaction';
|
||||
import Empty from './Empty';
|
||||
import Edges from './Edges';
|
||||
import Validation from './Validation';
|
||||
import Horizontal from './Horizontal';
|
||||
import Provider from './Provider';
|
||||
import Hidden from './Hidden';
|
||||
import EdgeTypes from './EdgeTypes';
|
||||
@@ -35,11 +34,6 @@ const routes = [
|
||||
component: CustomNode,
|
||||
label: 'CustomNode',
|
||||
},
|
||||
{
|
||||
path: '/horizontal',
|
||||
component: Horizontal,
|
||||
label: 'Horizontal',
|
||||
},
|
||||
{
|
||||
path: '/validation',
|
||||
component: Validation,
|
||||
@@ -88,24 +82,13 @@ const routes = [
|
||||
|
||||
const navLinks = routes.filter((route) => route.label);
|
||||
|
||||
const SourceDisplay = withRouter(({ location }) => {
|
||||
const route = routes.find((route) => route.path === location.pathname);
|
||||
const sourceLink = `https://github.com/wbkd/react-flow/tree/main/example/src/${route.label}/index.js`;
|
||||
|
||||
return (
|
||||
<a className="sourcedisplay" href={sourceLink}>
|
||||
{'<Source />'}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
const Header = () => {
|
||||
const [menuOpen, setMenuOpen] = useState();
|
||||
|
||||
return (
|
||||
<header>
|
||||
<a className="logo" href="https://github.com/wbkd/react-flow">
|
||||
React Flow
|
||||
React Flow Dev
|
||||
</a>
|
||||
<nav className={menuOpen ? 'is-open' : ''}>
|
||||
{navLinks.map((route) => (
|
||||
@@ -124,7 +107,6 @@ const Header = () => {
|
||||
ReactDOM.render(
|
||||
<Router forceRefresh={true}>
|
||||
<Header />
|
||||
<SourceDisplay />
|
||||
<Switch>
|
||||
{routes.map((route) => (
|
||||
<Route exact path={route.path} render={() => <route.component />} key={route.path} />
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
"build": "rollup -c --environment NODE_ENV:production",
|
||||
"start": "rollup -w -c",
|
||||
"dev": "npm run build && npm start & cd example && npm start",
|
||||
"start:examples": "npm run build && cd example && npm start",
|
||||
"dev:wait": "start-server-and-test start:examples http-get://localhost:3000",
|
||||
"build:example": "npm install && npm run build && cd example && npm install && npm run build",
|
||||
"start:dev": "npm run build && cd example && npm start",
|
||||
"dev:wait": "start-server-and-test start:dev http-get://localhost:3000",
|
||||
"build:dev": "npm install && npm run build && cd example && npm install && npm run build",
|
||||
"build:website": "cd website && npm install && GATSBY_EXPERIMENTAL_PAGE_BUILD_ON_DATA_CHANGES=true npm run build",
|
||||
"cy:open": "cypress open",
|
||||
"cypress": "npm run dev:wait cy:open",
|
||||
"test:chrome": "cypress run --browser chrome --headless",
|
||||
|
||||
18
website/.eslintrc.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
},
|
||||
plugins: ['react'],
|
||||
globals: {
|
||||
graphql: false,
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
extends: 'react-app',
|
||||
}
|
||||
9
website/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
public
|
||||
.gatsby-context.js
|
||||
.DS_Store
|
||||
.intermediate-representation/
|
||||
.cache/
|
||||
yarn.lock
|
||||
*.log
|
||||
.vscode
|
||||
1
website/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
v10
|
||||
1
website/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
src/pages/blog/**/*.md
|
||||
5
website/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
4
website/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# React Flow Website
|
||||
|
||||
The [react flow website](https://reactflow.dev) powered by Gatsby
|
||||
|
||||
6
website/gatsby-browser.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'babel-polyfill';
|
||||
import './src/styles/global.css';
|
||||
|
||||
export const onRouteUpdate = () => {
|
||||
document.body.classList.remove('noscroll');
|
||||
};
|
||||
94
website/gatsby-config.js
Normal file
@@ -0,0 +1,94 @@
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: `React Flow`,
|
||||
siteUrl: `https://reactflow.dev`,
|
||||
description:
|
||||
'React Flow is a highly customizable library for building interactive node-based editors, flow charts and diagrams.',
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: 'pages',
|
||||
path: `${__dirname}/src/pages`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: `markdown`,
|
||||
path: `${__dirname}/src/markdown`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: `assets`,
|
||||
path: `${__dirname}/src/assets`,
|
||||
ignore: [`**/.*`], // ignore files starting with a dot
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-plugin-manifest`,
|
||||
options: {
|
||||
icon: `src/assets/images/react-flow-logo.svg`,
|
||||
name: `React Flow`,
|
||||
short_name: `react-flow`,
|
||||
start_url: `/`,
|
||||
background_color: `#f7f0eb`,
|
||||
theme_color: `#1A192B`,
|
||||
display: `standalone`,
|
||||
lang: 'en',
|
||||
},
|
||||
},
|
||||
`gatsby-plugin-sharp`,
|
||||
`gatsby-remark-images`,
|
||||
{
|
||||
resolve: `gatsby-transformer-remark`,
|
||||
options: {
|
||||
plugins: [
|
||||
{
|
||||
resolve: `gatsby-remark-images`,
|
||||
options: {
|
||||
linkImagesToOriginal: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-plugin-mdx`,
|
||||
options: {
|
||||
gatsbyRemarkPlugins: [
|
||||
{
|
||||
resolve: `gatsby-remark-images`,
|
||||
options: {
|
||||
maxWidth: 1200,
|
||||
showCaptions: true,
|
||||
quality: 85,
|
||||
backgroundColor: 'white',
|
||||
linkImagesToOriginal: false,
|
||||
},
|
||||
},
|
||||
'gatsby-remark-copy-linked-files',
|
||||
],
|
||||
remarkPlugins: [require('remark-unwrap-images')],
|
||||
extensions: [`.md`, `.mdx`],
|
||||
},
|
||||
},
|
||||
`gatsby-transformer-json`,
|
||||
`gatsby-plugin-react-helmet`,
|
||||
`gatsby-plugin-emotion`,
|
||||
{
|
||||
resolve: `gatsby-plugin-react-svg`,
|
||||
options: {
|
||||
rule: {
|
||||
include: /icons/,
|
||||
},
|
||||
},
|
||||
},
|
||||
`gatsby-transformer-sharp`,
|
||||
`gatsby-plugin-netlify`,
|
||||
`gatsby-transformer-javascript-frontmatter`,
|
||||
],
|
||||
};
|
||||
40
website/gatsby-node.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const path = require('path');
|
||||
const { createFilePath } = require('gatsby-source-filesystem');
|
||||
|
||||
const docsGenerator = require('./generators/docs');
|
||||
|
||||
exports.createPages = ({ graphql, actions }) => {
|
||||
const { createPage } = actions;
|
||||
|
||||
const generateDocs = docsGenerator(createPage, graphql);
|
||||
|
||||
return Promise.all([generateDocs]);
|
||||
};
|
||||
|
||||
exports.onCreateNode = ({ node, actions, getNode }) => {
|
||||
const { createNodeField } = actions;
|
||||
|
||||
if (node.internal.type === 'Mdx') {
|
||||
const value = createFilePath({ node, getNode });
|
||||
|
||||
createNodeField({
|
||||
name: 'slug',
|
||||
node,
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.onCreateWebpackConfig = ({ stage, actions, loaders }) => {
|
||||
actions.setWebpackConfig({
|
||||
resolve: {
|
||||
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
|
||||
alias: {
|
||||
'example-flows': path.resolve(__dirname, 'src', 'example-flows'),
|
||||
},
|
||||
},
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
});
|
||||
};
|
||||
66
website/generators/docs.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const path = require('path');
|
||||
|
||||
const docsTemplate = path.resolve('./src/templates/doc-page.js');
|
||||
|
||||
const docPagesQuery = `
|
||||
{
|
||||
docs: allMdx(
|
||||
filter: {
|
||||
fields: {
|
||||
slug: { regex: "/docs/" },
|
||||
}
|
||||
}
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
frontmatter {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const createDocPages = (createPage, docPages) => {
|
||||
const menu = docPages.map((page) => ({
|
||||
slug: page.node.fields.slug,
|
||||
title: page.node.frontmatter.title,
|
||||
}));
|
||||
|
||||
docPages.forEach((page) => {
|
||||
createPage({
|
||||
path: page.node.fields.slug,
|
||||
component: docsTemplate,
|
||||
context: {
|
||||
slug: page.node.fields.slug,
|
||||
menu,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const blogGenerator = (createPage, graphql) =>
|
||||
new Promise((resolve, reject) => {
|
||||
resolve(
|
||||
graphql(docPagesQuery).then((result) => {
|
||||
if (result.errors) {
|
||||
console.log(result.errors);
|
||||
reject(result.errors);
|
||||
}
|
||||
|
||||
const docPages = result.data.docs.edges;
|
||||
|
||||
try {
|
||||
createDocPages(createPage, docPages);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
module.exports = blogGenerator;
|
||||
63
website/generators/markdown-pages.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const path = require('path');
|
||||
const utils = require('./utils');
|
||||
|
||||
const defaultTemplate = path.resolve('./src/templates/default-page.js');
|
||||
|
||||
const mdQuery = `
|
||||
{
|
||||
pages: allMdx {
|
||||
edges {
|
||||
node {
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
frontmatter {
|
||||
published
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const filterSpecialPages = (mdPage) => {
|
||||
const slug = mdPage.node.fields.slug;
|
||||
return slug.indexOf('/blog') !== 0 && slug.indexOf('/projects') !== 0;
|
||||
};
|
||||
|
||||
const createMdPages = (createPage, mdPages) => {
|
||||
mdPages
|
||||
.filter(filterSpecialPages)
|
||||
.filter(utils.filterPublished)
|
||||
.forEach((mdPage) => {
|
||||
createPage({
|
||||
path: mdPage.node.fields.slug,
|
||||
component: defaultTemplate,
|
||||
context: {
|
||||
slug: mdPage.node.fields.slug,
|
||||
fields: mdPage.node.fields,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const mdPagesGenerator = (createPage, graphql) =>
|
||||
new Promise((resolve, reject) => {
|
||||
resolve(
|
||||
graphql(mdQuery).then((result) => {
|
||||
if (result.errors) {
|
||||
console.log(result.errors);
|
||||
reject(result.errors);
|
||||
}
|
||||
|
||||
const mdPages = result.data.pages.edges;
|
||||
try {
|
||||
createMdPages(createPage, mdPages);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
module.exports = mdPagesGenerator;
|
||||
8
website/generators/utils.js
Normal file
@@ -0,0 +1,8 @@
|
||||
function filterPublished(post) {
|
||||
const isPublished = post.node.frontmatter && post.node.frontmatter.published;
|
||||
return process.env.NODE_ENV === 'development' || isPublished;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
filterPublished,
|
||||
};
|
||||
2
website/netlify.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[[plugins]]
|
||||
package = "netlify-plugin-gatsby-cache"
|
||||
30
website/node-scripts/download-fonts.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
curl -o ../static/fonts/NTDapper-thin.woff2 https://nodotypefoundry.com/fonts/ZDJ0TVNqY3pRa3BxYjBoWWRHVlRja0oxY1ZwUmVEWjVlR1o1YzNGVGRqQmlSQzlYUVhGd05HeEtSSEJxYmpVNGNtMWxZMVpyVDBaVmNIUk5LelpOVW04eWVrWlNZamR3TldWNGJrUnpjMjVKUkRGV1NGRTlQVG82Z3FzV3lOQ2w5eTVYY2c3NDExV1RSVG82TWpVME1RPT0=.woff2
|
||||
curl -o ../static/fonts/NTDapper-thin.woff https://nodotypefoundry.com/fonts/Vm10cVR6Z3hXbUZRYTNkTmQydHdiazhyYnpSTGNHcHlZbEZRV1ZveVFuQXdkRXhSYnpsTFRVeFJaVFYzVHpCVk9XOVpTbWN5TTBOUlN6Sm9ZbWR6V213NVp6Qm9TRGRPUnpGaU9YbE1ha1YyUzFkM00wRTlQVG82TmVMYXF3TGZBQnlVOHJ4QXY4cFdIem82TWpVME1RPT0=.woff
|
||||
curl -o ../static/fonts/NTDapper-thin.eot https://nodotypefoundry.com/fonts/UkdvMlNVbG5WQ3RHUVd0aVlWRjJNMmRtTm5ONFlWSklUR1ZYVUd3eFdtazJkRGRVVVZaR2IyTTVkbTVVYVZKeFVtd3lhbEJDTW1obFVWTk9kM1JQWTAxNGJVeGtUSFZ6TVZkM2FtWTFTa3AxVVZaelNFRTlQVG82a1o2V2pXL1hjdlhVRWFCN1lkbTUyam82TWpVME1RPT0=.eot
|
||||
|
||||
curl -o ../static/fonts/NTDapper-extralight.woff2 https://nodotypefoundry.com/fonts/TDNGeGFrSkRZbEp5ZDJnMVFscFNVM0pCTTFselZqTjNOVXRTV1hCMlJGVlpkVTl3UW1abGJVZFFiWFpWY1ZSblZFcEtkVWg1ZEc1bmRraEJhMnhvZEhGSmVUTnZOM1Z4TDJvclQycDZPVFJuZGpKbFFsRTlQVG82eFJlWVZWWUgrdC9uelFUSXJiaEhuVG82TWpVME1RPT0=.woff2
|
||||
curl -o ../static/fonts/NTDapper-extralight.woff https://nodotypefoundry.com/fonts/TmxSS2EyWTRlV2xvTmxwbmVYVnlZVFE0WnpNeWJYTnNaalo1VnpCNWVHa3plbnBaVGtKbU5EQjBRMUp4TUVSeU1pdE5ZMmRuTkhRd2IzSlBlbkU0Y1VOWFkwVllVR2xNYUc5RlVtaEJZbFp1V0RkSlNXYzlQVG82REVDWWpKN3pEbEdvbzN6RnN4NFAyVG82TWpVME1RPT0=.woff
|
||||
curl -o ../static/fonts/NTDapper-extralight.eot https://nodotypefoundry.com/fonts/VW5FMU5uZHlZVzU2U0hWV1RqWm9NMjgyYjFWMlVqRkNURlF4YWxNd2IyVTRkRVEwZWs0MWQwYzNWMUJXTjFwU2RrMHhNRmx3TlZCYU9HMHZkbXR2UW5CWWEwMVRlbWhyUjBsSWRtVjJWSHBHSzBSNE5uYzlQVG82S2Z0SVRJQm5XMy9YOEt5VS94OEdnVG82TWpVME1RPT0=.eot
|
||||
|
||||
curl -o ../static/fonts/NTDapper-light.woff2 https://nodotypefoundry.com/fonts/VldoRlMwaFdWMnhMVlZJMU0wcHlZazFJTkZSdFZUbE1OSGwxT0VoTlJtazNXVkJxU2xwd1oxUnJibEJGVEd0T01FTnpRMEk1TjJsdVFqRllPR0o0VURCaWIyeG1WR3RoT1RKTVdrVnZiR0pDUVZBek1tYzlQVG82RWhKeFJXcVZLeTl1WUgva3pZZ2Yzam82TWpVME1RPT0=.woff2
|
||||
curl -o ../static/fonts/NTDapper-light.woff https://nodotypefoundry.com/fonts/WmxkQ1ptMHdTakZ4U1RWWVIxVXdSVWxOUnpWSmJXaElSVUV4YWs4eVFsaFBkamM1THk5d1NYWXZabkZ0UTNGR1RXUjRXbnBxVVhZNVMwMUNNVW93Tnk5M2VXWlRPVTB4WnpsTWVTOTBRV3hwTm01NVdFRTlQVG82UkltekRSdUN4aVlzZXh3dlJ5MW1yem82TWpVME1RPT0=.woff
|
||||
curl -o ../static/fonts/NTDapper-light.eot https://nodotypefoundry.com/fonts/VGpsTWFVTnBjMFJVY0dSNFZtWlFNSGxOTWxOdFRURm1jSGxpU0hKbFJuUlhlVGwwWmtOMWNHaFdkMGRTZWxGSlNFNXZjWFpJTVVvcldtSkVOelJSYlhadmNFdFVMMGdyU1Raek9FYzBZVmh3ZVVkMmEyYzlQVG82TVR0OXZieThhYlk0Z2VJdm5xeUViem82TWpVME1RPT0=.eot
|
||||
|
||||
curl -o ../static/fonts/NTDapper-regular.woff2 https://nodotypefoundry.com/fonts/ZW1kYU1VSndOVE5EWVVKa2RWbHZNRzF0U0dNd2JHbzFWbEJTY1VSWmMxTndTVEJxUWpaSWR6ZGpkM1V5TkROTE0xTXdOMFZ5VEdsTmJqSnFRMlpDVXpkblVEbFFjWFJLV2tWcmN6SnFWRU0zWWk5SE9IYzlQVG82dk1pSVFnYXBVS2kvNkp1TUVsYi9vVG82TWpVME1RPT0=.woff2
|
||||
curl -o ../static/fonts/NTDapper-regular.woff https://nodotypefoundry.com/fonts/Um1KM1VXaHZaMlZCWlhkbWFrRm9hMUY2VWtWVVIzQktaMG8yVUhnMVdESldOM1pMVURObk9VRnBjbEV5TVZwNFZ6UTRSMFUwU1VSTWFIQndTbGd6ZVRoamVIVlZWalZ4ZDB4bWREaHlUSGhCVUdob2RrRTlQVG82aEdJdWs3VFZtM0JSZWttbWFWQ28vVG82TWpVME1RPT0=.woff
|
||||
curl -o ../static/fonts/NTDapper-regular.eot https://nodotypefoundry.com/fonts/V1dZM01EUTBWWFJNWjJwaWVsTmhkazUxZUhnMVVGVjRPR2xFV1drMGQyaFVOazl0ZFVZNE9VMVNRVGczUjJaS01ISXhhbWxqY1hvMVFXWjJSa3htUTIxUVpWbEZOa1V5VlhZMU9GWnViRVJhTVZwTGVHYzlQVG82UXRFTlVkbmJaa0p0anZ6bFZyNUZaVG82TWpVME1RPT0=.eot
|
||||
|
||||
|
||||
curl -o ../static/fonts/NTDapper-medium.woff2 https://nodotypefoundry.com/fonts/ZFdwd1NWbFdOVUZrWVRaSGVuWXZXVFJwU21KT1ZEVXJVRzB4T0ZvekswWk5VM2d6YTJZMWNHMW1jRTg0YzJrM2R6WnNWVTkxV21kaU1uQmpSQ3N6T1VzNVFsUktlbEJYWldrNWN6aFNhRFJIUVZSeFFrRTlQVG82V09yckl0STl1TDJIUmQxK0JtcGVzVG82TWpVME1RPT0=.woff2
|
||||
curl -o ../static/fonts/NTDapper-medium.woff https://nodotypefoundry.com/fonts/UjAxUFVIaG5lWGxtT0hWTWVYUmhRVkJWYVRCcVRqTkZPRTByTlZkTlpVdG9kMk5pTjJKaFNqWXJSbkJLUWxabGFHZ3ljbXRDVVVWSGJGZHVVVFZRTW1aeVJGWnlaa0V4WlhCTVVqaHlXV05GYUhWNlluYzlQVG82QWlEendjMjZRNDQ2R1BtVmJKbVRUem82TWpVME1RPT0=.woff
|
||||
curl -o ../static/fonts/NTDapper-medium.eot https://nodotypefoundry.com/fonts/T1d3d2FYaHlNWGs0ZFZGeGNXbElkVzlyYkhGUlNVVlBjVkozUVhsWWR6SlFOR00zYkVGQlNIWTROelExVGxwNVdVVlZXbEp6YzNoTFpVVXJSMlZ4Y1ZKMlF6RXpaWHBZVmxSM2JHNVZRa0ZwYVRKM04wRTlQVG82MUh0TW5nMG9qZHdhUzNsSmQ4YmZzVG82TWpVME1RPT0=.eot
|
||||
|
||||
|
||||
curl -o ../static/fonts/NTDapper-bold.woff2 https://nodotypefoundry.com/fonts/VHpsUVEyWlRlVmh5TWxsYU56aGxSMHNyVjFac1JYVlpValpvTlZOelFqbFVNRlpqUkhKUk0yaGhVRGRsYVd4VFJIRXlielk0UTNWemJWSmFiVzVEV2xoNGFrdHFXWE5JWmtkR2VVMXlNbkZNWTNSRFFYYzlQVG82ZzNjVW9hTDZpbE56ZGZIQ2l5cHdJem82TWpVME1RPT0=.woff2
|
||||
curl -o ../static/fonts/NTDapper-bold.woff https://nodotypefoundry.com/fonts/V0c5elowNDJORWd4WjBkS2VXNVpUVk5HTXpCUUwwVjVWMVJtY1V4TWEwdHVURFZCUVdobVVuQlpPR3BRYzJKYWVVMWxOMlpwTDA4MFlVRkpPRFl4Ykc1UVVsQjJiek5ZV1RCalptbDZTRFZTVURrd1FrRTlQVG82VGNHNzViL1VEOHdYVUU2dG1FdC9SVG82TWpVME1RPT0=.woff
|
||||
curl -o ../static/fonts/NTDapper-bold.eot https://nodotypefoundry.com/fonts/U1VFMlRYSTRhR2xXTlU1SGVESmplSFF4U1cxNGVXbERUV3BQVTJrMU5FczVVa1JGT0ZSdVVFMVVjVXBtTkc1SlQxZ3hiWEpqU1c5cE5VSlFRMVpWWm10ME9FeGlUMmx6VW1WbWFUQnlWblFyV2pSWE1sRTlQVG82c2UvSkEvTmdnQ0lTU3RqZ0MxOE8wRG82TWpVME1RPT0=.eot
|
||||
|
||||
|
||||
curl -o ../static/fonts/NTDapper-black.woff2 https://nodotypefoundry.com/fonts/VG1Vdk5rUnRNSEF2Y1VsTGFuQm9NQzluTTJsWWVVdHplaXRUUW1aYWRHVmpPRTQxU0hNemEzaGlTa05yYWtKaGJWVkdNVWxwUTFCYVJYRmFSMjA0YkZkTFRsQmFRWFI2YW5ad1ptdzVjQzlrZFhaQ1JsRTlQVG82UndqVFIxVmFLUEN0aGQvNDFLVnpqem82TWpVME1RPT0=.woff2
|
||||
curl -o ../static/fonts/NTDapper-black.woff https://nodotypefoundry.com/fonts/U3pOMVZsSm9VRTk2U2xWelRVWXlWVXhZTkVVemNGSlJWRXhxYkhWNlpVVTNSMlZrY0U0MlluUlBialJaUlZGRFZETXpZWFpFVDFSUFFXSnJZbFF3TW5oemVYUlFOVzFzVXpCS1YzQkNjWGRhWVVSeWJXYzlQVG82czRtQ2pqdmt5VkxWc1U0NzdOeGlWRG82TWpVME1RPT0=.woff
|
||||
curl -o ../static/fonts/NTDapper-black.eot https://nodotypefoundry.com/fonts/ZFU5NVdXWnVNMGRvYkhGTWFHOXNja2RVUzNoekwyVlFlSFpQUm5SUFIzVnhWMWhMYjBGT05IbDNLMXBsUlVsb09VOVVSMEpxTlRCSk5rcGFWMlZaT0dsTlFsTnRNREJGZHpCa1FtdFhPREZ3VUVkMU9GRTlQVG82eTROSXZ5ZDFXWGxiVkFUYkNmVE9TRG82TWpVME1RPT0=.eot
|
||||
71
website/node-scripts/download-github-data.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Octokit } = require('@octokit/rest');
|
||||
const octokit = new Octokit({
|
||||
auth: 'f5e6f805ea497290e162230f0e319ed7fde11cab',
|
||||
});
|
||||
|
||||
const outputPath = path.resolve(__dirname, '..', 'src/assets/data/github.json');
|
||||
|
||||
const getRepos = async () => {
|
||||
const repos = await octokit.repos.listForOrg({
|
||||
org: 'wbkd',
|
||||
per_page: 100,
|
||||
sort: 'created',
|
||||
});
|
||||
|
||||
return repos.data;
|
||||
};
|
||||
|
||||
const getData = async (repos) => {
|
||||
return Promise.all(
|
||||
repos.map(async (repo) => {
|
||||
const stats = await octokit.repos.getCommitActivityStats({
|
||||
owner: 'wbkd',
|
||||
repo: repo.name,
|
||||
});
|
||||
|
||||
return {
|
||||
repo: repo.name,
|
||||
stats: stats.data,
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const formatYearlyStats = (stats) => {
|
||||
return stats.reduce((year, current) => {
|
||||
return year.concat(current.days);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const formatData = (data) => {
|
||||
return data
|
||||
.reduce((result, repoData) => {
|
||||
if (repoData.stats) {
|
||||
result.push(formatYearlyStats(repoData.stats));
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [])
|
||||
.filter((values) => values.some((value) => value > 0))
|
||||
.map((values) =>
|
||||
values.reduce((result, value, i) => {
|
||||
if (value > 0) {
|
||||
result.push([i, value]);
|
||||
}
|
||||
return result;
|
||||
}, [])
|
||||
)
|
||||
.map((values) => ({ v: values }));
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const repos = await getRepos();
|
||||
const data = await getData(repos);
|
||||
const outputData = formatData(data);
|
||||
|
||||
console.log(outputData);
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(outputData));
|
||||
})();
|
||||
24248
website/package-lock.json
generated
Normal file
84
website/package.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "react-flow-website",
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"author": "webkid",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wbkd/react-flow/issues"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.25%",
|
||||
"ie 11"
|
||||
],
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.35",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@mdx-js/mdx": "^1.6.17",
|
||||
"@mdx-js/react": "^1.6.17",
|
||||
"@react-hook/size": "^2.1.1",
|
||||
"@svgr/webpack": "^5.4.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"d3-array": "^2.7.1",
|
||||
"d3-color": "^2.0.0",
|
||||
"d3-scale": "^3.2.2",
|
||||
"d3-shape": "^2.0.0",
|
||||
"emotion-normalize": "^10.1.0",
|
||||
"emotion-theming": "^10.0.27",
|
||||
"gatsby": "^2.24.60",
|
||||
"gatsby-image": "^2.4.18",
|
||||
"gatsby-plugin-draft": "^0.0.5",
|
||||
"gatsby-plugin-emotion": "^4.3.11",
|
||||
"gatsby-plugin-manifest": "^2.4.29",
|
||||
"gatsby-plugin-mdx": "^1.2.40",
|
||||
"gatsby-plugin-netlify": "^2.3.15",
|
||||
"gatsby-plugin-react-helmet": "^3.3.11",
|
||||
"gatsby-plugin-react-svg": "^3.0.0",
|
||||
"gatsby-plugin-svgr": "^2.0.2",
|
||||
"gatsby-remark-copy-linked-files": "^2.3.15",
|
||||
"gatsby-remark-images": "^3.3.30",
|
||||
"gatsby-remark-unwrap-images": "^1.0.2",
|
||||
"gatsby-source-filesystem": "^2.3.30",
|
||||
"gatsby-transformer-javascript-frontmatter": "^2.3.13",
|
||||
"gatsby-transformer-json": "^2.4.12",
|
||||
"gatsby-transformer-remark": "^2.8.35",
|
||||
"prism-react-renderer": "^1.1.1",
|
||||
"rc-table": "^7.9.8",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-flow-renderer": "^6.1.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"reflexbox": "^4.0.6",
|
||||
"remark-unwrap-images": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/rest": "^18.0.6",
|
||||
"babel-preset-gatsby": "^0.5.10",
|
||||
"eslint": "^7.9.0",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"gatsby-cli": "^2.12.97",
|
||||
"gatsby-plugin-sharp": "^2.6.36",
|
||||
"gatsby-transformer-sharp": "^2.5.15",
|
||||
"gh-pages": "^3.1.0",
|
||||
"prettier": "^2.1.1"
|
||||
},
|
||||
"homepage": "https://reactflow.dev",
|
||||
"keywords": [
|
||||
"gatsby"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "n/a",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wbkd/react-flow.git"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npm run dev",
|
||||
"dev": "gatsby develop -H 0.0.0.0",
|
||||
"lint": "eslint --ext .js,.jsx --ignore-pattern public .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"format": "prettier --trailing-comma es5 --no-semi --single-quote --write 'src/**/*.js' 'src/**/*.md'",
|
||||
"build": "GATSBY_EXPERIMENTAL_PAGE_BUILD_ON_DATA_CHANGES=true gatsby build --log-pages",
|
||||
"deploy": "gatsby build --prefix-paths && gh-pages -d public",
|
||||
"fix-semi": "eslint --quiet --ignore-pattern node_modules --ignore-pattern public --parser babel-eslint --no-eslintrc --rule '{\"semi\": [2, \"never\"], \"no-extra-semi\": [2]}' --fix gatsby-node.js"
|
||||
}
|
||||
}
|
||||
7
website/src/assets/data/showcases.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"title": "Circles - Visual degree planner",
|
||||
"url": "https://circles360.github.io/",
|
||||
"image": "../images/showcases/circles-showcase.png"
|
||||
}
|
||||
]
|
||||
3
website/src/assets/icons/arrow_right.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="23" height="15" viewBox="0 0 23 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path className="nostroke" fill-rule="evenodd" clip-rule="evenodd" d="M18.4535 6.49989L13.3195 1.73268L14.6805 0.26709L21.6805 6.76709L22.4696 7.49988L21.6805 8.23268L14.6805 14.7327L13.3195 13.2671L18.4535 8.49989L0 8.49988V6.49988L18.4535 6.49989Z" fill="#1A192B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
1
website/src/assets/icons/arrow_right_circle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="28" viewBox="0 0 21 21" width="28" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#2a2e3b" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 2)"><circle cx="8.5" cy="8.5" r="8"/><path d="m11.621 6.379v4.242h-4.242" transform="matrix(.70710678 .70710678 .70710678 -.70710678 -3.227683 7.792317)"/><path d="m8.5 4.5v8" transform="matrix(0 1 -1 0 17 0)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 426 B |
5
website/src/assets/icons/close.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="21" y="21.4141" width="2" height="16.1754" transform="rotate(-45 21 21.4141)" class="nostroke" fill="white"/>
|
||||
<rect x="22" y="33" width="2" height="16.1754" transform="rotate(-135 22 33)" class="nostroke" fill="white"/>
|
||||
<circle cx="27" cy="27" r="26" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 397 B |
3
website/src/assets/icons/code.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.48265 8.328C4.42132 8.328 4.85865 7.89067 4.79465 7.016L4.63465 4.504C4.61332 4.06667 4.66665 3.67733 4.79465 3.336C4.92265 2.99467 5.11465 2.70667 5.37065 2.472C5.63732 2.22667 5.95199 2.04 6.31465 1.912C6.68799 1.784 7.10399 1.72 7.56265 1.72H8.65065V3.56H7.83465C7.45065 3.56 7.15199 3.65067 6.93865 3.832C6.72532 4.01333 6.62932 4.30133 6.65065 4.696L6.81065 7.208C6.84265 7.80533 6.66665 8.28 6.28265 8.632C5.90932 8.984 5.40799 9.17067 4.77865 9.192V9.272C5.41865 9.29333 5.92532 9.49067 6.29865 9.864C6.67199 10.2267 6.84265 10.7067 6.81065 11.304L6.65065 13.8C6.62932 14.152 6.72532 14.4293 6.93865 14.632C7.16265 14.824 7.46132 14.92 7.83465 14.92H8.65065V16.776H7.56265C7.11465 16.776 6.70399 16.7067 6.33065 16.568C5.95732 16.44 5.63732 16.2533 5.37065 16.008C5.11465 15.7627 4.91732 15.464 4.77865 15.112C4.65065 14.76 4.60265 14.3653 4.63465 13.928L4.79465 11.496C4.82665 11.0587 4.73599 10.728 4.52265 10.504C4.31999 10.28 3.97332 10.168 3.48265 10.168H1.57865V8.328H3.48265ZM11.4036 16.776V14.92H12.2196C12.6036 14.92 12.9023 14.8293 13.1156 14.648C13.3396 14.4667 13.4409 14.184 13.4196 13.8L13.2596 11.288C13.2169 10.6907 13.3823 10.216 13.7556 9.864C14.1396 9.512 14.6463 9.32533 15.2756 9.304V9.224C14.6356 9.20267 14.1289 9.01067 13.7556 8.648C13.3823 8.27467 13.2169 7.78933 13.2596 7.192L13.4196 4.696C13.4409 4.33333 13.3396 4.056 13.1156 3.864C12.8916 3.66133 12.5929 3.56 12.2196 3.56H11.4036V1.72H12.5076C12.9556 1.72 13.3609 1.78933 13.7236 1.928C14.0969 2.056 14.4116 2.24267 14.6676 2.488C14.9343 2.73333 15.1316 3.032 15.2596 3.384C15.3983 3.72533 15.4516 4.11467 15.4196 4.552L15.2596 7C15.2276 7.43733 15.3129 7.768 15.5156 7.992C15.7289 8.20533 16.0809 8.312 16.5716 8.312H18.4756V10.152H16.5716C15.6329 10.152 15.1956 10.5947 15.2596 11.48L15.4196 13.976C15.4409 14.4027 15.3876 14.7867 15.2596 15.128C15.1316 15.48 14.9396 15.7787 14.6836 16.024C14.4276 16.2693 14.1129 16.456 13.7396 16.584C13.3663 16.712 12.9556 16.776 12.5076 16.776H11.4036Z" fill="white" class="nostroke" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
5
website/src/assets/icons/eye.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="30" height="20" viewBox="0 0 30 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="14.5158" cy="10.0006" r="5.38829" stroke="white" stroke-width="1.48148"/>
|
||||
<path d="M27.0214 8.5334C27.7216 9.40104 27.7216 10.599 27.0214 11.4666C25.8703 12.8929 24.0857 14.8567 21.8995 16.4583C19.7074 18.0644 17.1761 19.2593 14.5161 19.2593C11.8561 19.2593 9.32487 18.0644 7.13272 16.4583C4.9466 14.8567 3.16191 12.8929 2.01087 11.4666C1.31069 10.599 1.31069 9.40104 2.01087 8.5334C3.16191 7.10708 4.9466 5.1433 7.13272 3.54168C9.32487 1.93565 11.8561 0.740741 14.5161 0.740741C17.1761 0.740741 19.7074 1.93565 21.8995 3.54168C24.0857 5.1433 25.8703 7.10708 27.0214 8.5334Z" stroke="white" stroke-width="1.48148"/>
|
||||
<circle cx="14.4447" cy="9.99999" r="3.33333" fill="white" class="nostroke" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 810 B |
9
website/src/assets/icons/favicon_stroke.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="342" height="342" viewBox="0 0 342 342" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="path-1-outside-1" maskUnits="userSpaceOnUse" x="6" y="40" width="330" height="271" fill="black">
|
||||
<rect fill="white" x="6" y="40" width="330" height="271"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19 48L123.5 290L171 180L218.5 290L323 48H228L114 48H19Z"/>
|
||||
</mask>
|
||||
<path d="M123.5 290L116.155 293.171L123.5 310.18L130.844 293.171L123.5 290ZM19 48V40H6.83145L11.6555 51.1715L19 48ZM171 180L178.344 176.829L171 159.82L163.655 176.829L171 180ZM218.5 290L211.155 293.171L218.5 310.18L225.844 293.171L218.5 290ZM323 48L330.345 51.1715L335.169 40H323V48ZM228 48L228 56H228V48ZM114 48L114 40H114V48ZM130.844 286.828L26.3445 44.8285L11.6555 51.1715L116.155 293.171L130.844 286.828ZM163.655 176.829L116.155 286.828L130.844 293.171L178.344 183.171L163.655 176.829ZM225.844 286.828L178.344 176.829L163.655 183.171L211.155 293.171L225.844 286.828ZM315.656 44.8285L211.155 286.828L225.844 293.171L330.345 51.1715L315.656 44.8285ZM228 56H323V40H228V56ZM114 56L228 56L228 40L114 40L114 56ZM19 56H114V40H19V56Z" fill="white" mask="url(#path-1-outside-1)"/>
|
||||
<path d="M123.5 290L19 48L228 48L123.5 290Z" fill="#C5CBD2"/>
|
||||
<path d="M218.5 290L114 48L323 48L218.5 290Z" fill="#1A192B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
website/src/assets/icons/github_circle.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="nostroke" d="M18.6586 5.10534C17.7643 3.53477 16.5514 2.29135 15.0194 1.37479C13.4871 0.458185 11.8144 0 9.99991 0C8.18569 0 6.51238 0.458325 4.98046 1.37479C3.4483 2.2913 2.23539 3.53477 1.34116 5.10534C0.447068 6.67587 0 8.39089 0 10.2504C0 12.484 0.635737 14.4926 1.90753 16.2766C3.17919 18.0607 4.82198 19.2952 6.83577 19.9803C7.07018 20.0249 7.24371 19.9936 7.35654 19.887C7.46941 19.7804 7.52578 19.6467 7.52578 19.4868C7.52578 19.4601 7.52354 19.2199 7.51921 18.766C7.51474 18.3122 7.51264 17.9162 7.51264 17.5783L7.21315 17.6315C7.0222 17.6673 6.78132 17.6825 6.49049 17.6782C6.19981 17.674 5.89804 17.6428 5.58559 17.5847C5.27302 17.5272 4.98228 17.3937 4.71317 17.1846C4.44419 16.9755 4.25324 16.7018 4.14036 16.3639L4.01016 16.0568C3.92337 15.8523 3.78674 15.6251 3.60008 15.3761C3.41342 15.1269 3.22466 14.958 3.03371 14.869L2.94254 14.8021C2.8818 14.7577 2.82543 14.704 2.7733 14.6418C2.72122 14.5796 2.68223 14.5173 2.65619 14.455C2.6301 14.3926 2.65172 14.3414 2.72127 14.3012C2.79081 14.261 2.9165 14.2416 3.09888 14.2416L3.35919 14.2814C3.53281 14.3171 3.74757 14.4236 4.00373 14.6017C4.25976 14.7796 4.47023 15.0109 4.63518 15.2956C4.83493 15.6605 5.07559 15.9385 5.35784 16.1299C5.63986 16.3212 5.92421 16.4167 6.21061 16.4167C6.49702 16.4167 6.74438 16.3945 6.95279 16.3502C7.16098 16.3057 7.35631 16.2388 7.53868 16.1499C7.61681 15.5535 7.82951 15.0953 8.17661 14.775C7.68189 14.7217 7.2371 14.6414 6.84202 14.5347C6.44717 14.4278 6.03914 14.2544 5.6182 14.0139C5.19704 13.7738 4.84766 13.4756 4.56997 13.1198C4.29223 12.7639 4.06429 12.2966 3.88648 11.7183C3.70857 11.1399 3.6196 10.4726 3.6196 9.71627C3.6196 8.63941 3.96255 7.72304 4.64832 6.96665C4.32707 6.15705 4.3574 5.24947 4.73939 4.244C4.99113 4.16382 5.36445 4.22399 5.85918 4.42412C6.354 4.62434 6.71628 4.79587 6.94641 4.93808C7.17653 5.08024 7.36092 5.20071 7.49983 5.29842C8.30727 5.06715 9.14052 4.95149 9.99982 4.95149C10.8591 4.95149 11.6925 5.06715 12.5 5.29842L12.9948 4.97823C13.3332 4.76459 13.7327 4.56881 14.1925 4.39083C14.6526 4.21295 15.0044 4.16396 15.2475 4.24414C15.638 5.24966 15.6728 6.15719 15.3515 6.96679C16.0372 7.72318 16.3803 8.63978 16.3803 9.71641C16.3803 10.4727 16.291 11.1421 16.1133 11.725C15.9355 12.308 15.7056 12.7749 15.4236 13.1265C15.1412 13.4781 14.7896 13.774 14.3687 14.0141C13.9476 14.2544 13.5395 14.4278 13.1446 14.5347C12.7496 14.6415 12.3048 14.7219 11.8101 14.7752C12.2613 15.1755 12.4869 15.8073 12.4869 16.6704V19.4864C12.4869 19.6464 12.5412 19.7799 12.6498 19.8867C12.7583 19.9932 12.9297 20.0246 13.1641 19.9799C15.1782 19.2949 16.8209 18.0603 18.0926 16.2762C19.364 14.4922 20 12.4837 20 10.25C19.9995 8.39075 19.5522 6.67587 18.6586 5.10534Z" fill="#F8F9F9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
1
website/src/assets/icons/graph_bar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="28" viewBox="0 0 21 21" width="28" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#2a2e3b" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 3)"><path d="m.5.5v12c0 1.1045695.8954305 2 2 2h11.5"/><path d="m3.5 8.5v3"/><path d="m7.5 5.5v6"/><path d="m11.5 2.5v9"/></g></svg>
|
||||
|
After Width: | Height: | Size: 337 B |
7
website/src/assets/icons/info_circle.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg height="28" viewBox="0 0 21 21" width="28" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<circle cx="10.5" cy="10.5" r="8" stroke="#2a2e3b" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="m10.5 14.5v-4" stroke="#2a2e3b" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="10.5" cy="7.5" fill="#2a2e3b" r="1" class="nostroke" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 405 B |
1
website/src/assets/icons/lightning.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="28" viewBox="0 0 21 21" width="28" xmlns="http://www.w3.org/2000/svg"><path d="m6.5 7.5h4l-6 9v-6.997l-4-.003 6-9z" fill="none" stroke="#2a2e3b" stroke-linecap="round" stroke-linejoin="round" transform="translate(5 2)"/></svg>
|
||||
|
After Width: | Height: | Size: 239 B |
1
website/src/assets/icons/link.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="28" viewBox="0 0 21 21" width="28" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#2a2e3b" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 4)"><path d="m4.17157288 4.87867966v-1.41421357c0-1.56209716 1.26632995-2.82842712 2.82842712-2.82842712s2.82842712 1.26632996 2.82842712 2.82842712v1.41421357m0 2.82842712v2.82842712c0 1.5620972-1.26632995 2.8284271-2.82842712 2.8284271s-2.82842712-1.2663299-2.82842712-2.8284271v-2.82842712" transform="matrix(.70710678 .70710678 -.70710678 .70710678 7 -2.899495)"/><path d="m4.5 9.5 5-5"/></g></svg>
|
||||
|
After Width: | Height: | Size: 607 B |
1
website/src/assets/icons/mail.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="28" viewBox="0 0 21 21" width="28" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#2a2e3b" stroke-linecap="round" stroke-linejoin="round" transform="matrix(0 -1 1 0 2.5 16.5)"><path d="m2 .5h8c1.1045695 0 2 .8954305 2 2v10c0 1.1045695-.8954305 2-2 2h-8c-1.1045695 0-2-.8954305-2-2v-10c0-1.1045695.8954305-2 2-2z"/><path d="m2 6 5 3 5-3" transform="matrix(0 1 -1 0 14.5 .5)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 429 B |
12
website/src/assets/icons/menu.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="32" height="20" viewBox="0 0 32 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<rect y="2" width="2" height="32" transform="rotate(-90 0 2)" fill="white" class="nostroke"/>
|
||||
<rect y="13" width="2" height="32" transform="rotate(-90 0 13)" fill="white" class="nostroke"/>
|
||||
<rect y="24" width="2" height="32" transform="rotate(-90 0 24)" fill="white" class="nostroke"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="32" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 515 B |
11
website/src/assets/icons/phone.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="28" height="28" viewBox="0 0 22 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="6.21875" y="0.5" width="9.62081" height="15.3397" rx="1.13397" stroke="#F8F9F9"/>
|
||||
<mask id="path-2-inside-1" fill="white">
|
||||
<rect x="8.16992" width="5.7189" height="2.45096" rx="0.816985"/>
|
||||
</mask>
|
||||
<rect class="nostroke" x="8.16992" width="5.7189" height="2.45096" rx="0.816985" stroke="#F8F9F9" stroke-width="2" mask="url(#path-2-inside-1)"/>
|
||||
<path d="M3.89531 10.0226C3.70565 9.83295 3.5552 9.60779 3.45256 9.35999C3.34991 9.11218 3.29708 8.84659 3.29708 8.57837C3.29708 8.31015 3.34991 8.04455 3.45256 7.79675C3.5552 7.54895 3.70565 7.32379 3.89531 7.13413L4.34941 7.58823C4.21938 7.71826 4.11624 7.87262 4.04587 8.04251C3.9755 8.2124 3.93928 8.39448 3.93928 8.57837C3.93928 8.76225 3.9755 8.94434 4.04587 9.11423C4.11624 9.28412 4.21938 9.43848 4.34941 9.56851L3.89531 10.0226Z" class="nostroke" fill="#F8F9F9"/>
|
||||
<path d="M18.1047 7.13413C18.2944 7.32379 18.4448 7.54895 18.5474 7.79675C18.6501 8.04456 18.7029 8.31015 18.7029 8.57837C18.7029 8.84659 18.6501 9.11218 18.5474 9.35999C18.4448 9.60779 18.2944 9.83295 18.1047 10.0226L17.6506 9.56851C17.7806 9.43848 17.8838 9.28412 17.9541 9.11423C18.0245 8.94434 18.0607 8.76225 18.0607 8.57837C18.0607 8.39448 18.0245 8.2124 17.9541 8.04251C17.8838 7.87262 17.7806 7.71826 17.6506 7.58823L18.1047 7.13413Z" class="nostroke" fill="#F8F9F9"/>
|
||||
<path d="M2.84487 11.4232C2.47128 11.0496 2.17493 10.6061 1.97274 10.118C1.77056 9.62987 1.66649 9.1067 1.66649 8.57837C1.66649 8.05003 1.77056 7.52687 1.97274 7.03875C2.17493 6.55063 2.47128 6.10712 2.84487 5.73353L3.30299 6.19165C2.98956 6.50508 2.74094 6.87717 2.57131 7.28669C2.40169 7.6962 2.31438 8.13511 2.31438 8.57837C2.31438 9.02162 2.40169 9.46054 2.57131 9.87005C2.74094 10.2796 2.98956 10.6517 3.30299 10.9651L2.84487 11.4232Z" class="nostroke" fill="#F8F9F9"/>
|
||||
<path d="M19.1546 5.73353C19.5282 6.10712 19.8246 6.55063 20.0268 7.03875C20.229 7.52687 20.333 8.05003 20.333 8.57837C20.333 9.10671 20.229 9.62987 20.0268 10.118C19.8246 10.6061 19.5282 11.0496 19.1546 11.4232L18.6965 10.9651C19.0099 10.6517 19.2586 10.2796 19.4282 9.87005C19.5978 9.46054 19.6851 9.02162 19.6851 8.57837C19.6851 8.13511 19.5978 7.6962 19.4282 7.28669C19.2586 6.87717 19.0099 6.50508 18.6965 6.19165L19.1546 5.73353Z" class="nostroke" fill="#F8F9F9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
3
website/src/assets/icons/twitter_circle.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="nostroke" d="M10 20C15.5224 20 20 15.5224 20 10C20 4.47755 15.5224 0 10 0C4.47755 0 0 4.47755 0 10C0 15.5224 4.47755 20 10 20ZM7.06122 7.43878C8.98367 8.4102 10.6082 8.35306 10.6082 8.35306C10.6082 8.35306 9.99184 6.20612 11.8959 5.2551C13.8 4.30408 15.1102 5.90816 15.1102 5.90816C15.1102 5.90816 15.4429 5.81633 15.6898 5.72653C15.9367 5.63469 16.2959 5.47347 16.2959 5.47347L15.7102 6.52041L16.6143 6.42449C16.6143 6.42449 16.502 6.58775 16.1408 6.91837C15.7796 7.24898 15.6306 7.42041 15.6306 7.42041C15.6306 7.42041 15.7592 10.0041 14.398 11.9939C13.0367 13.9837 11.2776 15.1776 8.72245 15.4286C6.16735 15.6796 4.50408 14.6449 4.50408 14.6449C4.50408 14.6449 5.62041 14.5816 6.33265 14.3082C7.0449 14.0367 8.06939 13.3184 8.06939 13.3184C8.06939 13.3184 6.61429 12.8694 6.0898 12.3673C5.56735 11.8633 5.43673 11.5653 5.43673 11.5653L6.87347 11.5469C6.87347 11.5469 5.36122 10.7449 4.93265 10.1102C4.50408 9.47551 4.44694 8.85918 4.44694 8.85918L5.55102 9.30612C5.55102 9.30612 4.63265 8.05714 4.50204 7.08571C4.37143 6.11429 4.66939 5.59184 4.66939 5.59184C4.66939 5.59184 5.13878 6.46735 7.06122 7.43878Z" fill="#F8F9F9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
website/src/assets/icons/twitter_outline.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 3.95833C21.4055 4.25085 21.4054 4.25096 21.4053 4.25107L21.4052 4.2513L21.4048 4.25178L21.4041 4.25279L21.4025 4.25504L21.3984 4.26043L21.3875 4.27484C21.3788 4.28605 21.3673 4.3005 21.3526 4.3182C21.3233 4.35358 21.2813 4.40201 21.2237 4.46358C21.1086 4.58667 20.9308 4.76278 20.6671 4.99275C20.4214 5.20711 20.2499 5.36805 20.1416 5.47352C20.1377 5.47735 20.1338 5.4811 20.1301 5.48478C20.1306 5.5866 20.1294 5.72037 20.1237 5.88107C20.1102 6.26173 20.0716 6.79576 19.9713 7.41567C19.7716 8.64957 19.322 10.2554 18.3096 11.6658C16.3297 14.424 13.7448 16.0968 10.0203 16.4455C8.16106 16.6196 6.62325 16.3481 5.54671 16.0309C5.00842 15.8723 4.58465 15.7021 4.29244 15.5697C4.14627 15.5035 4.03284 15.4467 3.95425 15.4055C3.91495 15.3848 3.88435 15.3681 3.8627 15.356L3.83697 15.3414L3.8292 15.3369L3.82659 15.3353L3.82561 15.3348L3.82521 15.3345C3.82502 15.3344 3.82485 15.3343 4.07984 14.9042L3.82485 15.3343L2.40719 14.4938L4.05275 14.405L4.05277 14.405L4.05279 14.405L4.05287 14.4049L4.05322 14.4049L4.0558 14.4048L4.0674 14.4041L4.11508 14.401C4.15723 14.3982 4.21932 14.3937 4.29724 14.3871C4.45332 14.374 4.67174 14.3528 4.9199 14.3207C5.42536 14.2553 6.01914 14.1487 6.46287 13.9863L6.464 13.9859C6.89448 13.8295 7.43562 13.5412 7.89953 13.2703C7.85761 13.2545 7.81506 13.2382 7.77205 13.2215C7.17842 12.9909 6.40256 12.6461 5.9584 12.2409L5.95694 12.2396C5.5711 11.8848 5.3218 11.5951 5.16494 11.3852C5.08652 11.2803 5.03129 11.1955 4.99372 11.1327C4.97494 11.1013 4.96059 11.0754 4.94997 11.0553C4.94466 11.0452 4.94029 11.0366 4.93677 11.0295L4.93212 11.0199L4.93025 11.0159L4.92942 11.0141L4.92904 11.0133L4.92885 11.0129C4.92876 11.0127 4.92867 11.0125 5.38293 10.8036L4.92867 11.0125L4.60692 10.313L5.37683 10.3036L5.58523 10.3011C5.40249 10.1789 5.21981 10.0495 5.04849 9.91658C4.75412 9.68824 4.46335 9.42707 4.27066 9.1551C3.9336 8.67939 3.74314 8.21163 3.63652 7.8621C3.58306 7.68685 3.55016 7.53962 3.53034 7.4335C3.52041 7.38038 3.51373 7.33737 3.50937 7.30608C3.50719 7.29042 3.5056 7.27768 3.50446 7.26805L3.5031 7.25595L3.50265 7.2517L3.50248 7.25002L3.50241 7.2493L3.50238 7.24897C3.50236 7.24881 3.50235 7.24866 4 7.20024L3.50235 7.24866L3.42388 6.44206L4.17997 6.73376L4.28567 6.77453C3.98088 6.22476 3.67168 5.54509 3.58189 4.90863C3.48133 4.19582 3.54424 3.63487 3.63951 3.24146C3.68699 3.04536 3.74219 2.89216 3.78819 2.78354C3.81118 2.72924 3.83188 2.68608 3.84816 2.65425C3.85631 2.63834 3.86335 2.62525 3.86904 2.61501L3.87651 2.60179L3.87943 2.59679L3.88067 2.59469L3.88123 2.59374L3.8815 2.59329C3.88163 2.59307 3.88176 2.59286 4.3108 2.84961L3.88176 2.59286L4.32416 1.85357L4.74423 2.60027C4.74429 2.60038 4.74436 2.60049 4.74443 2.6006C4.74637 2.60375 4.75108 2.61125 4.7589 2.62285C4.77452 2.64605 4.80263 2.68573 4.8459 2.7399C4.93239 2.84817 5.07993 3.01486 5.31034 3.2238C5.77094 3.64147 6.56536 4.23034 7.86957 4.85841C9.16 5.47985 10.3503 5.77208 11.2158 5.9088C11.524 5.95749 11.7905 5.98635 12.0047 6.00336C11.9711 5.6978 11.9568 5.30368 12.0148 4.87208C12.1495 3.8695 12.6784 2.67058 14.1927 1.94976C15.6848 1.23952 16.9676 1.48277 17.8654 1.90958C18.3085 2.12025 18.6564 2.37417 18.894 2.57523C18.9512 2.62369 19.0024 2.66935 19.0472 2.71094C19.201 2.66688 19.3873 2.61096 19.5429 2.55711C19.7035 2.50012 19.9055 2.41987 20.0718 2.35183C20.1544 2.31809 20.2264 2.28806 20.2777 2.2665L20.3376 2.24118L20.3535 2.23446L20.3574 2.23278L20.3583 2.23238L20.3585 2.2323L20.3585 2.23229L21 3.95833ZM21 3.95833L21.4055 4.25085L22.0561 3.34899L20.9497 3.46086L20.6668 3.48946M21 3.95833L20.6668 3.48946M20.6668 3.48946L20.9864 2.94512L21.7557 1.63462L20.3585 2.23228L20.6668 3.48946Z" stroke="#F8F9F9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
BIN
website/src/assets/images/blog-webkid-teaser.jpg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
18
website/src/assets/images/logo-white.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="600" height="128" viewBox="0 0 600 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M55.1521 128L1.88195e-05 -9.6431e-06L110.304 0L55.1521 128Z" fill="#C5CBD2"/>
|
||||
<path d="M105.152 128L50 -9.6431e-06L160.304 0L105.152 128Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M321.911 0H448.853L321.911 106.517V0Z" fill="#C5CBD2"/>
|
||||
<rect x="175" width="89.6584" height="128" fill="#F8F9F9"/>
|
||||
<rect x="472.82" width="67.4658" height="128" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M264.215 51.021C278.187 51.021 289.514 39.5995 289.514 25.5105C289.514 11.4214 278.187 0 264.215 0C250.242 0 238.915 11.4214 238.915 25.5105C238.915 39.5995 250.242 51.021 264.215 51.021Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M263.771 127.999C284.852 127.999 301.942 110.767 301.942 89.5095C301.942 68.2524 284.852 51.02 263.771 51.02C242.689 51.02 225.599 68.2524 225.599 89.5095C225.599 110.767 242.689 127.999 263.771 127.999Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M536.291 128C571.346 128 599.762 99.3462 599.762 64C599.762 28.6538 571.346 0 536.291 0C501.237 0 472.82 28.6538 472.82 64C472.82 99.3462 501.237 128 536.291 128Z" fill="#F8F9F9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M321.911 0L448.853 128H321.911V0Z" fill="#F8F9F9"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="600" height="128" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
18
website/src/assets/images/logo.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="600" height="128" viewBox="0 0 600 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M55.1521 128L1.88195e-05 -9.6431e-06L110.304 0L55.1521 128Z" fill="#53606C"/>
|
||||
<path d="M105.152 128L50 -9.6431e-06L160.304 0L105.152 128Z" fill="#1A192B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M321.911 0H448.853L321.911 106.517V0Z" fill="#53606C"/>
|
||||
<rect x="175" width="89.6584" height="128" fill="#1A192B"/>
|
||||
<rect x="472.82" width="67.4658" height="128" fill="#1A192B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M264.215 51.021C278.187 51.021 289.514 39.5995 289.514 25.5105C289.514 11.4214 278.187 0 264.215 0C250.242 0 238.915 11.4214 238.915 25.5105C238.915 39.5995 250.242 51.021 264.215 51.021Z" fill="#1A192B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M263.771 127.999C284.852 127.999 301.942 110.767 301.942 89.5095C301.942 68.2524 284.852 51.02 263.771 51.02C242.689 51.02 225.599 68.2524 225.599 89.5095C225.599 110.767 242.689 127.999 263.771 127.999Z" fill="#1A192B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M536.291 128C571.346 128 599.762 99.3462 599.762 64C599.762 28.6538 571.346 0 536.291 0C501.237 0 472.82 28.6538 472.82 64C472.82 99.3462 501.237 128 536.291 128Z" fill="#1A192B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M321.911 0L448.853 128H321.911V0Z" fill="#1A192B"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="600" height="128" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
22
website/src/assets/images/react-flow-logo.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<rect x="23" y="23" width="14" height="14" rx="2" fill="white" stroke="#1A192B" stroke-width="2"/>
|
||||
<rect x="23" y="3" width="14" height="14" rx="2" fill="white" stroke="#FF0072" stroke-width="2"/>
|
||||
<rect x="3" y="23" width="14" height="14" rx="2" fill="white" stroke="#1A192B" stroke-width="2"/>
|
||||
<rect x="3" y="3" width="14" height="14" rx="2" fill="white" stroke="#1A192B" stroke-width="2"/>
|
||||
<circle cx="17" cy="10" r="3" fill="white"/>
|
||||
<circle cx="23" cy="10" r="3" fill="white"/>
|
||||
<circle cx="30" cy="17" r="3" fill="white"/>
|
||||
<circle cx="30" cy="23" r="3" fill="white"/>
|
||||
<circle cx="17" cy="30" r="3" fill="white"/>
|
||||
<circle cx="23" cy="30" r="3" fill="white"/>
|
||||
<circle cx="30" cy="23" r="2" fill="#1A192B"/>
|
||||
<circle cx="17" cy="30" r="2" fill="#1A192B"/>
|
||||
<circle cx="23" cy="30" r="2" fill="#1A192B"/>
|
||||
<rect opacity="0.35" x="18" y="9.5" width="4" height="1" fill="#1A192B"/>
|
||||
<rect opacity="0.35" x="29.5" y="21.5" width="4" height="1" transform="rotate(-90 29.5 21.5)" fill="#1A192B"/>
|
||||
<rect opacity="0.35" x="18" y="29.5" width="4" height="1" fill="#1A192B"/>
|
||||
<circle cx="17" cy="10" r="2" fill="#1A192B"/>
|
||||
<circle cx="23" cy="10" r="2" fill="#FF0072"/>
|
||||
<circle cx="30" cy="17" r="2" fill="#FF0072"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
BIN
website/src/assets/images/showcases/circles-showcase.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
88
website/src/components/Button/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { rgba } from 'utils/css-utils';
|
||||
import { Flex } from 'reflexbox';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import { getThemeColor } from 'utils/css-utils';
|
||||
|
||||
const Button = styled.button`
|
||||
color: ${(p) => p.theme.colors[p.color || 'button']};
|
||||
border: ${(p) =>
|
||||
p.type === 'ghost'
|
||||
? '1px solid rgba(0,0,0,0)'
|
||||
: `1px solid ${p.theme.colors[p.color || 'button']}`};
|
||||
background: ${(p) =>
|
||||
p.active
|
||||
? rgba(p.color ? p.theme.colors[p.color] : p.theme.colors.button, 0.2)
|
||||
: 'none'};
|
||||
padding: ${(p) => (p.type === 'big' ? '12px 20px' : '8px 16px')};
|
||||
border-radius: 25px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
font-size: ${(p) => p.theme.fontSizesPx[1]};
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 1;
|
||||
transition: 0.075s all ease-in-out;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:visited,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: ${(p) => p.theme.colors[p.color || 'button']};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: ${getThemeColor('background')};
|
||||
background-color: ${getThemeColor('button')};
|
||||
|
||||
svg {
|
||||
path,
|
||||
circle,
|
||||
rect,
|
||||
line,
|
||||
polyline {
|
||||
stroke: ${getThemeColor('background')};
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
.nostroke {
|
||||
stroke: none;
|
||||
fill: ${getThemeColor('background')};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
`;
|
||||
|
||||
export default ({
|
||||
icon,
|
||||
color = 'button',
|
||||
children,
|
||||
type = 'normal',
|
||||
iconWidth = '20px',
|
||||
...props
|
||||
}) => (
|
||||
<Button color={color} type={type} {...props}>
|
||||
<Flex alignItems="center" justifyContent="center">
|
||||
{icon && (
|
||||
<Icon
|
||||
strokeColor={color}
|
||||
name={icon}
|
||||
colorizeStroke
|
||||
width={iconWidth}
|
||||
mr={1}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</Flex>
|
||||
</Button>
|
||||
);
|
||||
13
website/src/components/CenterContent/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Box } from 'reflexbox';
|
||||
|
||||
import { getThemeSpacePx } from 'utils/css-utils';
|
||||
|
||||
export default styled(Box)`
|
||||
max-width: ${(p) =>
|
||||
p.maxWidth || (p.big ? p.theme.maxWidthBig : p.theme.maxWidth)};
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: ${getThemeSpacePx(3)};
|
||||
padding-right: ${getThemeSpacePx(3)};
|
||||
`;
|
||||
23
website/src/components/Close/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex } from 'reflexbox';
|
||||
|
||||
import { getThemeSpacePx } from 'utils/css-utils';
|
||||
import Icon from 'components/Icon';
|
||||
|
||||
const Close = styled(Flex)`
|
||||
padding: ${getThemeSpacePx(1)};
|
||||
line-height: 1;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
`;
|
||||
|
||||
export default ({ onClick, ...props }) => {
|
||||
return (
|
||||
<Flex pb={4} alignItems="center" justifyContent="center" {...props}>
|
||||
<Close alignItems="center" justifyContent="center" onClick={onClick}>
|
||||
<Icon name="close" strokeColor="text" colorizeStroke />
|
||||
</Close>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
45
website/src/components/CodeBlock/Mdx.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import Highlight, { defaultProps } from 'prism-react-renderer';
|
||||
import dracula from 'prism-react-renderer/themes/github';
|
||||
|
||||
export default ({ children, className = 'javascript', language }) => {
|
||||
const lang = language ? language : className.replace(/language-/, '');
|
||||
|
||||
return (
|
||||
<Highlight
|
||||
{...defaultProps}
|
||||
theme={dracula}
|
||||
code={children}
|
||||
language={lang}
|
||||
>
|
||||
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
||||
<pre
|
||||
className={className}
|
||||
style={{
|
||||
...style,
|
||||
overflowX: 'auto',
|
||||
padding: 30,
|
||||
borderRadius: 4,
|
||||
margin: '20px 0 20px 0',
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{tokens.map(
|
||||
(line, i, lines) =>
|
||||
!(
|
||||
line.length === 1 &&
|
||||
line[0].empty &&
|
||||
(i === 0 || i === lines.length - 1)
|
||||
) && (
|
||||
<div key={i} {...getLineProps({ line, key: i })}>
|
||||
{line.map((token, key) => (
|
||||
<span key={key} {...getTokenProps({ token, key })} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
);
|
||||
};
|
||||
42
website/src/components/CodeBlock/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Box } from 'reflexbox';
|
||||
|
||||
import { colors } from 'themes';
|
||||
import CodeBlock from './Mdx';
|
||||
import { H4, AttributionText } from 'components/Typo';
|
||||
|
||||
const Wrapper = styled(Box)`
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 5px 0;
|
||||
|
||||
h4 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
code {
|
||||
border: 1px solid ${colors.lightGrey};
|
||||
border-radius: 3px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default ({
|
||||
language = 'javascript',
|
||||
code,
|
||||
title = '',
|
||||
subtitle = '',
|
||||
}) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
{title && <H4>{title}</H4>}
|
||||
<CodeBlock language={language}>{code}</CodeBlock>
|
||||
{subtitle && <AttributionText>{subtitle}</AttributionText>}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
22
website/src/components/ContentSection/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Box } from 'reflexbox';
|
||||
|
||||
import CenterContent from 'components/CenterContent';
|
||||
|
||||
export default ({
|
||||
children,
|
||||
bg = 'transparent',
|
||||
centered = false,
|
||||
id = null,
|
||||
big = false,
|
||||
...rest
|
||||
}) => {
|
||||
const WrapperComponent = centered ? CenterContent : Fragment;
|
||||
const wrapperProps = centered ? { big: big } : {};
|
||||
|
||||
return (
|
||||
<Box id={id} bg={bg} py={[6, 7]} {...rest}>
|
||||
<WrapperComponent {...wrapperProps}>{children}</WrapperComponent>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
48
website/src/components/Footer/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex } from 'reflexbox';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import Logo from 'components/Logo';
|
||||
|
||||
const Wrapper = styled.footer`
|
||||
flex-shrink: 0;
|
||||
border-top: ${(p) =>
|
||||
p.hasBorder ? `1px solid ${p.theme.colors.silverLighten30}` : 'nonde'};
|
||||
`;
|
||||
|
||||
const Header = ({ hasBorder = false }) => {
|
||||
return (
|
||||
<Wrapper hasBorder={hasBorder}>
|
||||
<Flex p={4}>
|
||||
<Flex alignItems="center">
|
||||
A project by
|
||||
<a href="https://webkid.io" style={{ marginLeft: 8 }}>
|
||||
<Logo />
|
||||
</a>
|
||||
</Flex>
|
||||
<Flex ml="auto">
|
||||
<Icon
|
||||
as="a"
|
||||
href="https://github.com/wbkd"
|
||||
name="github_circle"
|
||||
colorizeStroke
|
||||
strokeColor="text"
|
||||
style={{ marginRight: 8 }}
|
||||
width="24px"
|
||||
/>
|
||||
<Icon
|
||||
as="a"
|
||||
href="https://twitter.com/webk1d"
|
||||
name="twitter_circle"
|
||||
colorizeStroke
|
||||
strokeColor="text"
|
||||
width="24px"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
240
website/src/components/Header/index.js
Normal file
@@ -0,0 +1,240 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'gatsby';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex, Box } from 'reflexbox';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import Button from 'components/Button';
|
||||
import Close from 'components/Close';
|
||||
import { getThemeColor, getThemeSpacePx, device, px } from 'utils/css-utils';
|
||||
import useMenuHeight from 'hooks/useMenuHeight';
|
||||
|
||||
import ReactFlowLogo from 'assets/images/react-flow-logo.svg';
|
||||
|
||||
const Centered = styled(Flex)`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: ${getThemeSpacePx(4)};
|
||||
`;
|
||||
|
||||
const Wrapper = styled.nav`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const NavWrapper = styled(Box)`
|
||||
position: absolute;
|
||||
display: ${(p) => (p.menuOpen ? 'flex' : 'none')};
|
||||
height: ${(p) => px(p.height)};
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: ${getThemeSpacePx(3)};
|
||||
z-index: 500;
|
||||
background: ${getThemeColor('background')};
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media ${device.tablet} {
|
||||
display: block;
|
||||
height: auto;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin-left: auto;
|
||||
width: auto;
|
||||
background: transparent;
|
||||
align-items: unset;
|
||||
flex-direction: row;
|
||||
|
||||
.desktop {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Nav = styled.div`
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const NavInner = styled.div`
|
||||
line-height: 1;
|
||||
width: 100%;
|
||||
|
||||
@media ${device.tablet} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
width: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const NavItem = styled(Link)`
|
||||
text-decoration: none;
|
||||
font-size: 30px;
|
||||
display: block;
|
||||
padding: ${(p) => (p.isButton ? '8px 16px' : '16px 0')};
|
||||
text-align: center;
|
||||
color: ${getThemeColor('textLight')};
|
||||
|
||||
&:focus,
|
||||
&:visited,
|
||||
&:active {
|
||||
color: ${getThemeColor('textLight')};
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: ${getThemeColor('text')};
|
||||
}
|
||||
|
||||
@media ${device.tablet} {
|
||||
margin-left: 50px;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
padding: ${(p) => (p.isButton ? '8px 16px' : 0)};
|
||||
color: ${getThemeColor('textLight')};
|
||||
|
||||
&:focus,
|
||||
&:visited {
|
||||
color: ${getThemeColor('textLight')};
|
||||
}
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
color: ${getThemeColor('text')};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const GithubButton = styled.a`
|
||||
font-size: 30px;
|
||||
display: block;
|
||||
padding: ${(p) => (p.isButton ? '12px 24px' : '16px 0')} !important;
|
||||
text-align: center;
|
||||
color: ${getThemeColor('textLight')};
|
||||
|
||||
&:focus,
|
||||
&:visited,
|
||||
&:active {
|
||||
color: ${getThemeColor('textLight')};
|
||||
}
|
||||
|
||||
@media ${device.tablet} {
|
||||
margin-left: 50px;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
background: ${(p) => p.theme.colors.textDark};
|
||||
|
||||
&&& {
|
||||
&:hover {
|
||||
background: ${getThemeColor('red')};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const NavButton = styled.div`
|
||||
cursor: pointer;
|
||||
|
||||
@media ${device.tablet} {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const Image = styled.img`
|
||||
display: block;
|
||||
width: 40px;
|
||||
margin-right: 12px;
|
||||
`;
|
||||
|
||||
const LogoTitle = styled(Box)`
|
||||
font-weight: 900;
|
||||
font-size: 24px;
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 1.2;
|
||||
color: ${getThemeColor('violet')};
|
||||
`;
|
||||
|
||||
const LogoSubtitle = styled(Box)`
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
color: ${getThemeColor('silverDarken30')};
|
||||
line-height: 1.2;
|
||||
`;
|
||||
|
||||
const Header = () => {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const menuHeight = useMenuHeight();
|
||||
|
||||
const toggleMenu = () => {
|
||||
const nextMenuOpen = !menuOpen;
|
||||
|
||||
document.body.classList.toggle('noscroll', nextMenuOpen);
|
||||
|
||||
setMenuOpen(nextMenuOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Centered>
|
||||
<Flex as={Link} to="/">
|
||||
<Image src={ReactFlowLogo} alt="React Flow Logo" />
|
||||
<Box>
|
||||
<LogoTitle>React Flow</LogoTitle>
|
||||
<LogoSubtitle>an open source library by webkid.io</LogoSubtitle>
|
||||
</Box>
|
||||
</Flex>
|
||||
<NavButton onClick={toggleMenu}>
|
||||
<Icon name="menu" strokeColor="text" colorizeStroke />
|
||||
</NavButton>
|
||||
<NavWrapper menuOpen={menuOpen} height={menuHeight}>
|
||||
<Nav>
|
||||
<NavInner>
|
||||
<NavItem to="/" activeClassName="active" className="mobile">
|
||||
Home
|
||||
</NavItem>
|
||||
<NavItem to="/docs/" activeClassName="active" partiallyActive>
|
||||
Docs
|
||||
</NavItem>
|
||||
<NavItem to="/examples/" activeClassName="active" partiallyActive>
|
||||
Examples
|
||||
</NavItem>
|
||||
|
||||
<Button
|
||||
as={GithubButton}
|
||||
href="https://github.com/wbkd/react-flow"
|
||||
activeClassName="active"
|
||||
icon="github_circle"
|
||||
className="desktop"
|
||||
isButton
|
||||
color="textInverted"
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
|
||||
<GithubButton
|
||||
href="https://github.com/wbkd/react-flow"
|
||||
activeClassName="active"
|
||||
className="mobile"
|
||||
isButton
|
||||
>
|
||||
Github
|
||||
</GithubButton>
|
||||
</NavInner>
|
||||
</Nav>
|
||||
<Close onClick={toggleMenu} className="mobile" />
|
||||
</NavWrapper>
|
||||
</Centered>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
72
website/src/components/HeroFlow/ColorPickerNode.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Handle } from 'react-flow-renderer';
|
||||
|
||||
import { getThemeColor } from 'utils/css-utils';
|
||||
|
||||
const ColorPickerNodeWrapper = styled.div`
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border: 1px solid ${getThemeColor('violet')};
|
||||
border-radius: 4px;
|
||||
box-shadow: ${(p) =>
|
||||
p.selected ? `0 0 0 0.25px ${p.theme.colors.violet}` : 'none'};
|
||||
|
||||
.react-flow__handle {
|
||||
background: ${(p) => p.color};
|
||||
}
|
||||
|
||||
input {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: ${getThemeColor('silver')};
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
input::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: ${(p) => p.color};
|
||||
cursor: pointer;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
input::-moz-range-thumb {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: ${(p) => p.color};
|
||||
cursor: pointer;
|
||||
border-radius: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const ColorPickerNode = memo(({ data, id, selected }) => {
|
||||
const onChange = useCallback((event) => data.onChange(event, id), [data, id]);
|
||||
const colorName = `${data.color[0].toUpperCase()}${data.color.substr(1)}`;
|
||||
|
||||
return (
|
||||
<ColorPickerNodeWrapper
|
||||
color={data.color}
|
||||
value={data.value}
|
||||
selected={selected}
|
||||
>
|
||||
<div>{colorName} amount</div>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="255"
|
||||
value={data.value}
|
||||
onChange={onChange}
|
||||
className="nodrag"
|
||||
/>
|
||||
<Handle type="source" position="right" />
|
||||
</ColorPickerNodeWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
export default ColorPickerNode;
|
||||
141
website/src/components/HeroFlow/index.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
Controls,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import ColorPickerNode from './ColorPickerNode';
|
||||
|
||||
const windowWidth = typeof window !== 'undefined' ? window.innerWidth : 0;
|
||||
const isSmallScreen = windowWidth < 800;
|
||||
|
||||
const getOffset = () => {
|
||||
if (isSmallScreen) {
|
||||
return 0;
|
||||
} else if (windowWidth < 1200) {
|
||||
return 425;
|
||||
} else {
|
||||
return (windowWidth - 1200) / 2 + 500;
|
||||
}
|
||||
};
|
||||
|
||||
const getColorNodeX = () => {
|
||||
if (windowWidth < 600) {
|
||||
return offsetLeft + 250;
|
||||
} else if (isSmallScreen) {
|
||||
return offsetLeft + 400;
|
||||
} else if (windowWidth < 1000) {
|
||||
return offsetLeft + 300;
|
||||
} else if (windowWidth < 1200) {
|
||||
return offsetLeft + 400;
|
||||
} else {
|
||||
return offsetLeft + 550;
|
||||
}
|
||||
};
|
||||
|
||||
const offsetLeft = getOffset();
|
||||
|
||||
const nodeTypes = {
|
||||
colorpicker: ColorPickerNode,
|
||||
};
|
||||
|
||||
const findNodeByColor = (color) => (n) =>
|
||||
n.type === 'colorpicker' && n.data.color === color;
|
||||
|
||||
export default () => {
|
||||
const [elements, setElements] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = (event, id) => {
|
||||
setElements((els) => {
|
||||
const nextElements = els.map((e) => {
|
||||
if (e.id !== id) {
|
||||
return e;
|
||||
}
|
||||
|
||||
const value = event.target.value;
|
||||
|
||||
return {
|
||||
...e,
|
||||
data: {
|
||||
...e.data,
|
||||
value,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const red = nextElements.find(findNodeByColor('red')).data.value;
|
||||
const green = nextElements.find(findNodeByColor('green')).data.value;
|
||||
const blue = nextElements.find(findNodeByColor('blue')).data.value;
|
||||
|
||||
const background = `rgb(${red}, ${green}, ${blue})`;
|
||||
const colorNode = nextElements.find((n) => n.id === '4');
|
||||
colorNode.style = {
|
||||
background,
|
||||
};
|
||||
colorNode.data = {
|
||||
...colorNode.data,
|
||||
label: background,
|
||||
};
|
||||
|
||||
return nextElements;
|
||||
});
|
||||
};
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'colorpicker',
|
||||
data: { color: 'red', value: 105, onChange },
|
||||
sourcePosition: 'right',
|
||||
position: { x: offsetLeft + 50, y: isSmallScreen ? 250 : 5 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'colorpicker',
|
||||
data: { color: 'green', value: 100, onChange },
|
||||
sourcePosition: 'right',
|
||||
position: { x: offsetLeft, y: isSmallScreen ? 325 : 150 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { color: 'blue', value: 165, onChange },
|
||||
type: 'colorpicker',
|
||||
sourcePosition: 'right',
|
||||
position: {
|
||||
x: isSmallScreen ? offsetLeft + 50 : offsetLeft + 120,
|
||||
y: isSmallScreen ? 400 : 300,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'rgb(105, 100, 165)' },
|
||||
targetPosition: 'left',
|
||||
sourcePosition: 'right',
|
||||
position: { x: getColorNodeX(), y: isSmallScreen ? 350 : 180 },
|
||||
style: {
|
||||
width: 200,
|
||||
background: 'rgb(105, 100, 165)',
|
||||
color: 'white',
|
||||
textShadow:
|
||||
'rgba(0, 0, 0, 0.4) 1px 0px 0px, rgba(0, 0, 0, 0.4) -1px 0px 0px, rgba(0, 0, 0, 0.4) 0px 1px 0px, rgba(0, 0, 0, 0.4) 0px -1px 0px',
|
||||
},
|
||||
},
|
||||
{ id: 'e1-4', source: '1', target: '4', animated: true },
|
||||
{ id: 'e2-4', source: '2', target: '4', animated: true },
|
||||
{ id: 'e3-4', source: '3', target: '4', animated: true },
|
||||
];
|
||||
|
||||
setElements(initialElements);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow elements={elements} zoomOnScroll={false} nodeTypes={nodeTypes}>
|
||||
<Background />
|
||||
<Controls showInteractive={false} />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
};
|
||||
103
website/src/components/Icon/index.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex } from 'reflexbox';
|
||||
import { isOldIE } from 'utils/browser-utils';
|
||||
|
||||
import Mail from 'assets/icons/mail.svg';
|
||||
import Github from 'assets/icons/github_circle.svg';
|
||||
import ArrowRight from 'assets/icons/arrow_right.svg';
|
||||
import Menu from 'assets/icons/menu.svg';
|
||||
import Code from 'assets/icons/code.svg';
|
||||
|
||||
const IconWrapper = styled(Flex)`
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
path,
|
||||
circle,
|
||||
rect,
|
||||
line,
|
||||
polyline {
|
||||
stroke: ${(p) =>
|
||||
p.colorizeStroke
|
||||
? p.theme.colors[p.strokeColor] || p.theme.colors.text
|
||||
: 'none'};
|
||||
}
|
||||
|
||||
path.nostroke,
|
||||
circle.nostroke,
|
||||
rect.nostroke,
|
||||
line.nostroke,
|
||||
polyline.nostroke {
|
||||
stroke: none;
|
||||
fill: ${(p) =>
|
||||
p.colorizeStroke
|
||||
? p.theme.colors[p.strokeColor] || p.theme.colors.text
|
||||
: 'none'};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const preLoaded = {
|
||||
mail: Mail,
|
||||
arrow_right: ArrowRight,
|
||||
menu: Menu,
|
||||
github_circle: Github,
|
||||
code: Code,
|
||||
};
|
||||
|
||||
const getPreLoadedIcon = (name) =>
|
||||
preLoaded[name] ? () => preLoaded[name] : null;
|
||||
|
||||
export default ({
|
||||
name = 'datavis',
|
||||
colorizeStroke = false,
|
||||
strokeColor = null,
|
||||
...restProps
|
||||
}) => {
|
||||
const [SVGComponent, setSVGComponent] = useState(getPreLoadedIcon(name));
|
||||
|
||||
useEffect(() => {
|
||||
if (!SVGComponent) {
|
||||
(async () => {
|
||||
try {
|
||||
const svgComp = await import(`assets/icons/${name}.svg`);
|
||||
setSVGComponent(svgComp);
|
||||
} catch (err) {
|
||||
console.warn(`error loading icon: ${name}`);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [SVGComponent, name]);
|
||||
|
||||
if (!SVGComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const IconComponent = SVGComponent.default
|
||||
? SVGComponent.default
|
||||
: SVGComponent;
|
||||
|
||||
if (isOldIE()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<IconWrapper
|
||||
className="icon"
|
||||
colorizeStroke={colorizeStroke}
|
||||
strokeColor={strokeColor}
|
||||
height={restProps.width || 'auto'}
|
||||
{...restProps}
|
||||
>
|
||||
<IconComponent />
|
||||
</IconWrapper>
|
||||
);
|
||||
};
|
||||
30
website/src/components/Logo/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useTheme } from 'emotion-theming';
|
||||
|
||||
import WbkdLogoWhite from 'assets/images/logo-white.svg';
|
||||
import WbkdLogoBlack from 'assets/images/logo.svg';
|
||||
|
||||
const Image = styled.img`
|
||||
display: block;
|
||||
width: 75px;
|
||||
`;
|
||||
|
||||
const getLogoSrc = (type, inverted, theme) => {
|
||||
if (type) {
|
||||
return type === 'white' ? WbkdLogoWhite : WbkdLogoBlack;
|
||||
}
|
||||
|
||||
if (inverted) {
|
||||
return theme.name === 'light' ? WbkdLogoWhite : WbkdLogoBlack;
|
||||
}
|
||||
|
||||
return theme.name === 'light' ? WbkdLogoBlack : WbkdLogoWhite;
|
||||
};
|
||||
|
||||
export default ({ type = null, inverted = false, style = {} }) => {
|
||||
const theme = useTheme();
|
||||
const logoSrc = getLogoSrc(type, inverted, theme);
|
||||
|
||||
return <Image src={logoSrc} alt="webkid logo" style={style} />;
|
||||
};
|
||||
26
website/src/components/Page/Doc.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex, Box } from 'reflexbox';
|
||||
|
||||
import Page from 'components/Page';
|
||||
import Sidebar from 'components/Sidebar';
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
border-top: 1px solid ${(p) => p.theme.colors.silverLighten30};
|
||||
`;
|
||||
|
||||
const DocWrapper = styled(Box)`
|
||||
max-width: 620px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
`;
|
||||
|
||||
export default ({ children, menu = [], ...rest }) => (
|
||||
<Page {...rest} footerBorder>
|
||||
<Wrapper>
|
||||
<Sidebar menu={menu} isDocs />
|
||||
<DocWrapper>{children}</DocWrapper>
|
||||
</Wrapper>
|
||||
</Page>
|
||||
);
|
||||
32
website/src/components/Page/Example.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex } from 'reflexbox';
|
||||
|
||||
import Page from 'components/Page';
|
||||
import Sidebar from 'components/Sidebar';
|
||||
import useExamplePages from 'hooks/useExamplePages';
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
border-top: 1px solid ${(p) => p.theme.colors.silverLighten30};
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
export default ({ children, title, slug }) => {
|
||||
const menu = useExamplePages();
|
||||
|
||||
const metaTags = {
|
||||
title: `React Flow - ${title} Example`,
|
||||
siteUrl: `https://reactflow.dev/examples/${slug}`,
|
||||
robots: 'index, follow',
|
||||
};
|
||||
|
||||
return (
|
||||
<Page metaTags={metaTags} footerBorder>
|
||||
<Wrapper>
|
||||
<Sidebar menu={menu} />
|
||||
{children}
|
||||
</Wrapper>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
50
website/src/components/Page/MetaTags.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
const MetaTags = ({
|
||||
title,
|
||||
description,
|
||||
siteUrl,
|
||||
robots,
|
||||
image,
|
||||
pathname,
|
||||
article,
|
||||
}) => {
|
||||
if (pathname) {
|
||||
siteUrl = `${siteUrl}${pathname}`;
|
||||
}
|
||||
|
||||
if (image) {
|
||||
image = `https://reactflow.dev${image}`;
|
||||
} else {
|
||||
image = 'https://reactflow.dev/images/react-flow-header.jpg';
|
||||
}
|
||||
|
||||
return (
|
||||
<Helmet defaultTitle="React Flow">
|
||||
<html lang="en" />
|
||||
<title>{title}</title>
|
||||
{description && <meta name="description" content={description} />}
|
||||
{description && <meta name="robots" content={robots} />}
|
||||
|
||||
{siteUrl && <meta property="og:url" content={siteUrl} />}
|
||||
{(article ? true : null) && <meta property="og:type" content="article" />}
|
||||
{title && <meta property="og:title" content={title} />}
|
||||
{description && <meta property="og:description" content={description} />}
|
||||
{image && <meta property="og:image" content={image} />}
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
{title && <meta name="twitter:title" content={title} />}
|
||||
{description && <meta name="twitter:description" content={description} />}
|
||||
{image && <meta name="twitter:image" content={image} />}
|
||||
|
||||
<script
|
||||
src="https://cdn.usefathom.com/script.js"
|
||||
site="LXMRMWLB"
|
||||
defer
|
||||
></script>
|
||||
</Helmet>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetaTags;
|
||||
51
website/src/components/Page/index.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ThemeProvider } from 'emotion-theming';
|
||||
import { Flex, Box } from 'reflexbox';
|
||||
|
||||
import Header from 'components/Header';
|
||||
import Footer from 'components/Footer';
|
||||
import themes from 'themes';
|
||||
|
||||
import NormalizeStyle from 'themes/normalize';
|
||||
import GlobalStyle from 'themes/global';
|
||||
import MetaTags from './MetaTags';
|
||||
import { getThemeColor } from 'utils/css-utils';
|
||||
|
||||
const PageWrapper = styled(Flex)`
|
||||
color: ${getThemeColor('text')};
|
||||
width: 100%;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const PageContent = styled(Box)`
|
||||
flex: 1 0 auto;
|
||||
`;
|
||||
|
||||
const Page = ({
|
||||
children,
|
||||
theme = 'light',
|
||||
metaTags,
|
||||
footerBorder = false,
|
||||
...rest
|
||||
}) => {
|
||||
const pageTheme = themes[theme];
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={pageTheme}>
|
||||
<MetaTags {...metaTags} />
|
||||
<NormalizeStyle />
|
||||
<GlobalStyle />
|
||||
<PageWrapper>
|
||||
<Header />
|
||||
<PageContent {...rest}>{children}</PageContent>
|
||||
<Footer hasBorder={footerBorder} />
|
||||
</PageWrapper>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
37
website/src/components/SectionIntro/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Box } from 'reflexbox';
|
||||
|
||||
import { H1, H4 } from 'components/Typo';
|
||||
import { getThemeSpacePx, getThemeColor } from 'utils/css-utils';
|
||||
|
||||
const SectionIntroWrapper = styled(Box)`
|
||||
text-align: center;
|
||||
/* padding-top: ${getThemeSpacePx(5)};
|
||||
padding-bottom: ${getThemeSpacePx(
|
||||
6
|
||||
)}; */
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
|
||||
${H1} {
|
||||
margin: 0 0 ${getThemeSpacePx(3)} 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const SectionSubtitle = styled(H4)`
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: ${getThemeColor('silverDarken30')};
|
||||
`;
|
||||
|
||||
const SectionIntro = ({ title = '', text = '', ...props }) => {
|
||||
return (
|
||||
<SectionIntroWrapper pt={[3, 3, 5]} pb={[4, 4, 6]} {...props}>
|
||||
<H1>{title}</H1>
|
||||
{text && <SectionSubtitle>{text}</SectionSubtitle>}
|
||||
</SectionIntroWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionIntro;
|
||||
92
website/src/components/Showcases/index.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex, Box } from 'reflexbox';
|
||||
import Img from 'gatsby-image';
|
||||
|
||||
import CenterContent from 'components/CenterContent';
|
||||
import useShowcaseImages from 'hooks/useShowcaseImages';
|
||||
import { H4, Text } from 'components/Typo';
|
||||
import { getThemeColor } from 'utils/css-utils';
|
||||
import reactFlowIconSrc from 'assets/images/react-flow-logo.svg';
|
||||
|
||||
const gridPadding = 2;
|
||||
|
||||
const RoundImage = styled(Img)`
|
||||
border-radius: 4px;
|
||||
height: 250px;
|
||||
transition: transform 200ms ease;
|
||||
`;
|
||||
|
||||
const EmptyCase = styled(Box)`
|
||||
border-radius: 4px;
|
||||
height: 250px;
|
||||
background: ${(p) => p.theme.colors.silverLighten60};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Title = styled(H4)`
|
||||
color: ${getThemeColor('textInverted')};
|
||||
margin: 16px 0 0 0;
|
||||
font-weight: 400;
|
||||
`;
|
||||
|
||||
const Link = styled.a`
|
||||
&&& {
|
||||
color: ${getThemeColor('silverDarken15')};
|
||||
|
||||
&:hover {
|
||||
color: ${getThemeColor('silverLighten15')};
|
||||
|
||||
${RoundImage} {
|
||||
transform: scale(1.025);
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Showcases = () => {
|
||||
const showcases = useShowcaseImages();
|
||||
const emptyShowCases = [...Array(3 - showcases.length).keys()];
|
||||
|
||||
return (
|
||||
<CenterContent>
|
||||
<Flex marginX={[0, 0, -gridPadding]} flexWrap="wrap">
|
||||
{showcases.map((showcase) => (
|
||||
<Box
|
||||
key={showcase.title}
|
||||
width={[1, 1, 1 / 3]}
|
||||
px={[0, 0, gridPadding]}
|
||||
mb={[3, 3, 0]}
|
||||
>
|
||||
<Link href={showcase.url} target="_blank" rel="noopener noreferrer">
|
||||
<RoundImage fluid={showcase.image.childImageSharp.fluid} />
|
||||
<Title>{showcase.title}</Title>
|
||||
|
||||
{showcase.url}
|
||||
</Link>
|
||||
</Box>
|
||||
))}
|
||||
{emptyShowCases.map((index) => (
|
||||
<Box
|
||||
key={index}
|
||||
width={[1, 1, 1 / 3]}
|
||||
px={[0, 0, gridPadding]}
|
||||
mb={[3, 3, 0]}
|
||||
>
|
||||
<EmptyCase>
|
||||
<img src={reactFlowIconSrc} alt="react flow logo" />
|
||||
</EmptyCase>
|
||||
<Title>Your Project here</Title>
|
||||
<Text color="silverDarken15">
|
||||
Let us know if you made something with React Flow.
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
</CenterContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default Showcases;
|
||||
131
website/src/components/Sidebar/index.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import Link from 'gatsby-link';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex } from 'reflexbox';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import Close from 'components/Close';
|
||||
import useMenuHeight from 'hooks/useMenuHeight';
|
||||
import { getThemeColor, getThemeSpacePx, device, px } from 'utils/css-utils';
|
||||
|
||||
const Aside = styled.aside`
|
||||
display: ${(p) => (p.isOpen ? 'block' : 'none')};
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: ${(p) => px(p.height)};
|
||||
overflow-y: auto;
|
||||
background: white;
|
||||
z-index: 400;
|
||||
|
||||
@media ${device.tablet} {
|
||||
width: 30%;
|
||||
max-width: 300px;
|
||||
padding: 16px;
|
||||
border-right: 1px solid ${getThemeColor('silverLighten30')};
|
||||
display: block;
|
||||
position: relative;
|
||||
height: auto;
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const AsideInner = styled.div``;
|
||||
|
||||
const MobileButton = styled(Flex)`
|
||||
z-index: 10;
|
||||
background: ${getThemeColor('violetLighten5')};
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
color: white;
|
||||
border-radius: 100%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@media ${device.tablet} {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const MenuLink = styled(Link)`
|
||||
display: block;
|
||||
padding: ${getThemeSpacePx(1)} ${getThemeSpacePx(2)};
|
||||
margin-left: ${(p) => (p.marginLeft ? `${p.marginLeft}px` : 0)};
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background: ${getThemeColor('silverLighten30')};
|
||||
}
|
||||
`;
|
||||
|
||||
const GroupLabel = styled.div`
|
||||
padding: ${getThemeSpacePx(1)} ${getThemeSpacePx(2)};
|
||||
color: ${getThemeColor('silverDarken60')};
|
||||
margin-left: ${(p) => (p.marginLeft ? `${p.marginLeft}px` : 0)};
|
||||
`;
|
||||
|
||||
const MenuItem = ({ title, slug, marginLeft }) => {
|
||||
return (
|
||||
<MenuLink to={slug} marginLeft={marginLeft} activeClassName="active">
|
||||
{title}
|
||||
</MenuLink>
|
||||
);
|
||||
};
|
||||
|
||||
const SideBarParts = ({ items, level }) =>
|
||||
items.map((menuItem) => {
|
||||
if (menuItem.title) {
|
||||
return (
|
||||
<MenuItem key={menuItem.slug} marginLeft={level * 16} {...menuItem} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment key={menuItem.group}>
|
||||
<GroupLabel marginLeft={level * 16}>{menuItem.group}</GroupLabel>
|
||||
<SideBarParts items={menuItem.items} level={level + 1} />
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
const Sidebar = ({ menu }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const menuHeight = useMenuHeight();
|
||||
|
||||
const toggleSidebar = () => {
|
||||
const nextMenuOpen = !isOpen;
|
||||
|
||||
document.body.classList.toggle('noscroll', nextMenuOpen);
|
||||
|
||||
setIsOpen(nextMenuOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MobileButton onClick={toggleSidebar}>
|
||||
<Icon
|
||||
name="menu"
|
||||
width="24px"
|
||||
colorizeStroke
|
||||
strokeColor="silverLighten60"
|
||||
/>
|
||||
</MobileButton>
|
||||
<Aside isOpen={isOpen} height={menuHeight}>
|
||||
<AsideInner>
|
||||
<SideBarParts items={menu} level={0} />
|
||||
<Close className="mobile" onClick={toggleSidebar} />
|
||||
</AsideInner>
|
||||
</Aside>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
88
website/src/components/TeaserFlow/A.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
|
||||
import TeaserFlow from 'components/TeaserFlow';
|
||||
|
||||
const elements = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
position: {
|
||||
x: 200,
|
||||
y: 5,
|
||||
},
|
||||
data: {
|
||||
label: 'Input',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
position: {
|
||||
x: 0,
|
||||
y: 150,
|
||||
},
|
||||
data: {
|
||||
label: 'Default',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
position: {
|
||||
x: 400,
|
||||
y: 150,
|
||||
},
|
||||
data: {
|
||||
label: 'Default',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'output',
|
||||
position: {
|
||||
x: 200,
|
||||
y: 300,
|
||||
},
|
||||
data: {
|
||||
label: 'Output',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e1',
|
||||
source: '1',
|
||||
target: '2',
|
||||
label: 'default edge',
|
||||
},
|
||||
{
|
||||
id: 'e2',
|
||||
source: '1',
|
||||
target: '3',
|
||||
animated: true,
|
||||
label: 'animated edge',
|
||||
},
|
||||
{
|
||||
id: 'e3',
|
||||
source: '2',
|
||||
target: '4',
|
||||
type: 'smoothstep',
|
||||
},
|
||||
{
|
||||
id: 'e4',
|
||||
source: '3',
|
||||
target: '4',
|
||||
type: 'smoothstep',
|
||||
},
|
||||
];
|
||||
|
||||
const flowProps = {
|
||||
elements,
|
||||
onLoad: (rf) => rf.fitView({ padding: 0.2 }),
|
||||
};
|
||||
|
||||
export default () => (
|
||||
<TeaserFlow
|
||||
title="Feature-rich"
|
||||
description="React Flow comes with seamless zooming & panning, different edge and node types, single and multi-selection, controls, several event handlers and more."
|
||||
flowProps={flowProps}
|
||||
withControls
|
||||
fitView
|
||||
/>
|
||||
);
|
||||
182
website/src/components/TeaserFlow/B.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import ReactFlow, {
|
||||
Handle,
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
Controls,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import TeaserFlow from 'components/TeaserFlow';
|
||||
import { baseColors } from 'themes';
|
||||
|
||||
const NodeWrapper = styled.div`
|
||||
background: ${(p) => p.theme.colors.silverLighten30};
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
border: 1px solid ${(p) => p.theme.colors.violetLighten60};
|
||||
width: 100%;
|
||||
border-radius: 2px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.react-flow__handle {
|
||||
background: ${(p) => p.theme.colors.violetLighten60};
|
||||
}
|
||||
`;
|
||||
|
||||
const InputLabel = styled.div`
|
||||
color: ${(p) => p.theme.colors.violetLighten60};
|
||||
`;
|
||||
|
||||
const InputNode = ({ id, data }) => {
|
||||
return (
|
||||
<NodeWrapper>
|
||||
<InputLabel>{data.label}:</InputLabel>
|
||||
<input
|
||||
className="nodrag"
|
||||
value={data.value}
|
||||
onChange={(event) => data.onChange(event, id)}
|
||||
/>
|
||||
<Handle type="source" position="bottom" />
|
||||
</NodeWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const ResultNode = ({ data }) => {
|
||||
return (
|
||||
<NodeWrapper>
|
||||
<div>{data.value}</div>
|
||||
<Handle type="target" position="top" id="a" style={{ left: '40%' }} />
|
||||
<Handle type="target" position="top" id="b" style={{ left: '60%' }} />
|
||||
</NodeWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const nodeTypes = {
|
||||
nameinput: InputNode,
|
||||
result: ResultNode,
|
||||
};
|
||||
|
||||
const onLoad = (rf) => setTimeout(() => rf.fitView({ padding: 0.1 }), 1);
|
||||
|
||||
const findNodeById = (id) => (n) => n.id === id;
|
||||
|
||||
export default () => {
|
||||
const [elements, setElements] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = (event, id) => {
|
||||
setElements((els) => {
|
||||
const nextElements = els.map((e) => {
|
||||
if (e.id !== id) {
|
||||
return e;
|
||||
}
|
||||
|
||||
const value = event.target.value;
|
||||
|
||||
return {
|
||||
...e,
|
||||
data: {
|
||||
...e.data,
|
||||
value,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const forname = nextElements.find(findNodeById('1')).data.value;
|
||||
const lastname = nextElements.find(findNodeById('2')).data.value;
|
||||
|
||||
const result = `${forname} ${lastname}`;
|
||||
const resultNode = nextElements.find((n) => n.type === 'result');
|
||||
|
||||
resultNode.data = {
|
||||
...resultNode.data,
|
||||
value: !forname && !lastname ? '*please enter a name*' : result,
|
||||
};
|
||||
|
||||
return nextElements;
|
||||
});
|
||||
};
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'nameinput',
|
||||
position: {
|
||||
x: 0,
|
||||
y: 200,
|
||||
},
|
||||
data: {
|
||||
label: 'Forename',
|
||||
value: 'React',
|
||||
onChange,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'nameinput',
|
||||
position: {
|
||||
x: 400,
|
||||
y: 200,
|
||||
},
|
||||
data: {
|
||||
label: 'Lastname',
|
||||
value: 'Flow',
|
||||
onChange,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'result',
|
||||
position: {
|
||||
x: 200,
|
||||
y: 400,
|
||||
},
|
||||
data: {
|
||||
value: 'React Flow',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'e1',
|
||||
source: '1',
|
||||
target: '3__a',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'e2',
|
||||
source: '2',
|
||||
target: '3__b',
|
||||
animated: true,
|
||||
},
|
||||
];
|
||||
|
||||
setElements(initialElements);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TeaserFlow
|
||||
title="Customizable"
|
||||
description="You can create your own node and edge types or just pass a custom style. You can implement custom UIs inside your nodes and add functionality to your edges."
|
||||
textPosition="right"
|
||||
fitView
|
||||
isDark
|
||||
>
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
nodeTypes={nodeTypes}
|
||||
onLoad={onLoad}
|
||||
zoomOnScroll={false}
|
||||
>
|
||||
<Background color={baseColors.silverDarken60} gap={15} />
|
||||
<Controls showInteractive={false} />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
</TeaserFlow>
|
||||
);
|
||||
};
|
||||
173
website/src/components/TeaserFlow/C.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import React from 'react';
|
||||
|
||||
import TeaserFlow from 'components/TeaserFlow';
|
||||
|
||||
const defaultNodeOptions = {
|
||||
targetPosition: 'left',
|
||||
sourcePosition: 'right',
|
||||
style: {
|
||||
width: 50,
|
||||
},
|
||||
};
|
||||
|
||||
const elements = [
|
||||
{
|
||||
id: 'input',
|
||||
type: 'input',
|
||||
position: {
|
||||
x: 0,
|
||||
y: 100,
|
||||
},
|
||||
data: {
|
||||
label: 'Input',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'A',
|
||||
position: {
|
||||
x: 150,
|
||||
y: 0,
|
||||
},
|
||||
data: {
|
||||
label: 'A',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
position: {
|
||||
x: 250,
|
||||
y: 0,
|
||||
},
|
||||
data: {
|
||||
label: 'B',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'C',
|
||||
position: {
|
||||
x: 350,
|
||||
y: 0,
|
||||
},
|
||||
data: {
|
||||
label: 'C',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'D',
|
||||
position: {
|
||||
x: 150,
|
||||
y: 200,
|
||||
},
|
||||
data: {
|
||||
label: 'D',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'E',
|
||||
position: {
|
||||
x: 250,
|
||||
y: 200,
|
||||
},
|
||||
data: {
|
||||
label: 'E',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'F',
|
||||
position: {
|
||||
x: 350,
|
||||
y: 200,
|
||||
},
|
||||
data: {
|
||||
label: 'F',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'output',
|
||||
type: 'output',
|
||||
position: {
|
||||
x: 500,
|
||||
y: 100,
|
||||
},
|
||||
data: {
|
||||
label: 'Output',
|
||||
},
|
||||
...defaultNodeOptions,
|
||||
},
|
||||
{
|
||||
id: 'e1',
|
||||
source: 'input',
|
||||
target: 'A',
|
||||
type: 'step',
|
||||
},
|
||||
{
|
||||
id: 'e2',
|
||||
source: 'A',
|
||||
target: 'B',
|
||||
type: 'step',
|
||||
},
|
||||
{
|
||||
id: 'e3',
|
||||
source: 'B',
|
||||
target: 'C',
|
||||
type: 'step',
|
||||
},
|
||||
{
|
||||
id: 'e4',
|
||||
source: 'C',
|
||||
target: 'output',
|
||||
type: 'step',
|
||||
},
|
||||
{
|
||||
id: 'e5',
|
||||
source: 'input',
|
||||
target: 'D',
|
||||
type: 'step',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'e6',
|
||||
source: 'D',
|
||||
target: 'E',
|
||||
type: 'step',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'e7',
|
||||
source: 'E',
|
||||
target: 'F',
|
||||
type: 'step',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'e8',
|
||||
source: 'F',
|
||||
target: 'output',
|
||||
type: 'step',
|
||||
animated: true,
|
||||
},
|
||||
];
|
||||
|
||||
const flowProps = {
|
||||
elements,
|
||||
onLoad: (rf) => rf.fitView({ padding: 0.2 }),
|
||||
};
|
||||
|
||||
export default () => (
|
||||
<TeaserFlow
|
||||
title="Additional Components"
|
||||
description="React Flow includes a MiniMap, Controls, Background and a FlowProvider you can use to access internal state outside the ReactFlow component."
|
||||
flowProps={flowProps}
|
||||
withControls
|
||||
withMinimap
|
||||
fitView
|
||||
linesBg
|
||||
/>
|
||||
);
|
||||
129
website/src/components/TeaserFlow/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex, Box } from 'reflexbox';
|
||||
import Link from 'gatsby-link';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
Background,
|
||||
Controls,
|
||||
MiniMap,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import { H2, Text } from 'components/Typo';
|
||||
import Icon from 'components/Icon';
|
||||
import { baseColors } from 'themes';
|
||||
import { device, getThemeSpacePx } from 'utils/css-utils';
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const ReactFlowWrapper = styled(Box)`
|
||||
height: 400px;
|
||||
background: ${(p) => (p.isDark ? baseColors.violet : 'white')};
|
||||
border-radius: 10px;
|
||||
order: 2;
|
||||
margin: 30px 0;
|
||||
border: solid rgba(26, 25, 43, 0.054) 1.5px;
|
||||
|
||||
box-shadow: 0 2.8px 2.2px rgba(26, 25, 43, 0.014),
|
||||
0 12.5px 10px rgba(26, 25, 43, 0.02),
|
||||
0 22.3px 17.9px rgba(26, 25, 43, 0.022),
|
||||
0 41.8px 33.4px rgba(26, 25, 43, 0.026), 0 100px 80px rgba(26, 25, 43, 0.02);
|
||||
|
||||
@media ${device.tablet} {
|
||||
order: ${(p) => p.order};
|
||||
}
|
||||
|
||||
.react-flow__controls {
|
||||
opacity: ${(p) => (p.isDark ? 0.5 : 1)};
|
||||
}
|
||||
`;
|
||||
|
||||
const DocsLink = styled(Link)`
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
|
||||
svg {
|
||||
transform: translateX(0px);
|
||||
transition: all 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
transform: translateX(5px);
|
||||
transition: all 0.125s ease-in-out;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DescriptionWrapper = styled(Box)`
|
||||
order: 1;
|
||||
margin-bottom: ${getThemeSpacePx(3)};
|
||||
|
||||
@media ${device.tablet} {
|
||||
order: ${(p) => p.order};
|
||||
}
|
||||
`;
|
||||
|
||||
const Description = ({ title, description }) => (
|
||||
<DescriptionWrapper width={[1, 1, 0.35]}>
|
||||
<H2>{title}</H2>
|
||||
<Text style={{ opacity: 0.7 }}>{description}</Text>
|
||||
<DocsLink to="/docs">
|
||||
Documentation{' '}
|
||||
<Icon width={42} name="arrow_right" colorizeStroke strokeColor="red" />
|
||||
</DocsLink>
|
||||
</DescriptionWrapper>
|
||||
);
|
||||
|
||||
export default ({
|
||||
title,
|
||||
description,
|
||||
textPosition = 'left',
|
||||
flowProps,
|
||||
withControls = false,
|
||||
withMinimap = false,
|
||||
isDark = false,
|
||||
linesBg = false,
|
||||
children,
|
||||
}) => {
|
||||
const bgColor = isDark ? baseColors.violetLighten60 : baseColors.violet;
|
||||
const reactFlowOrder = textPosition === 'left' ? 2 : 1;
|
||||
|
||||
return (
|
||||
<Wrapper mb={[6, 6, 7]}>
|
||||
{textPosition === 'left' && (
|
||||
<Description order={1} title={title} description={description} />
|
||||
)}
|
||||
<ReactFlowWrapper
|
||||
width={[1, 1, 0.6]}
|
||||
isDark={isDark}
|
||||
order={reactFlowOrder}
|
||||
>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow {...flowProps} zoomOnScroll={false}>
|
||||
<Background
|
||||
color={linesBg ? '#eee' : bgColor}
|
||||
gap={15}
|
||||
variant={linesBg ? 'lines' : 'dots'}
|
||||
/>
|
||||
{withControls && <Controls />}
|
||||
{withMinimap && <MiniMap />}
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
)}
|
||||
</ReactFlowWrapper>
|
||||
{textPosition !== 'left' && (
|
||||
<Description order={2} title={title} description={description} />
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
101
website/src/components/Typo/index.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Box } from 'reflexbox';
|
||||
|
||||
import { getThemeSpacePx, getThemeColor, device } from 'utils/css-utils';
|
||||
|
||||
export const H1 = styled.h1`
|
||||
font-size: ${(p) => p.theme.fontSizesPx[4]};
|
||||
line-height: 1.1;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 900;
|
||||
margin: ${getThemeSpacePx(3)} 0;
|
||||
|
||||
@media ${device.tablet} {
|
||||
font-size: ${(p) => p.theme.fontSizesPx[5]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const H2 = styled.h2`
|
||||
font-size: ${(p) => p.theme.fontSizesPx[3]};
|
||||
line-height: 1.1;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 900;
|
||||
margin: ${getThemeSpacePx(3)} 0;
|
||||
|
||||
@media ${device.tablet} {
|
||||
font-size: ${(p) => p.theme.fontSizesPx[4]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const H3 = styled.h3`
|
||||
font-size: ${(p) => p.theme.fontSizesPx[2]};
|
||||
line-height: 1.4;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 900;
|
||||
margin: ${getThemeSpacePx(3)} 0;
|
||||
|
||||
@media ${device.tablet} {
|
||||
font-size: ${(p) => p.theme.fontSizesPx[3]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const H4 = styled.h4`
|
||||
font-size: ${(p) => p.theme.fontSizesPx[1]};
|
||||
line-height: 1.3;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 900;
|
||||
margin: ${getThemeSpacePx(2)} 0;
|
||||
|
||||
@media ${device.tablet} {
|
||||
font-size: ${(p) => p.theme.fontSizesPx[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const H5 = H4;
|
||||
|
||||
export const UL = styled.ul`
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
`;
|
||||
|
||||
const BulletPointSvg = encodeURIComponent(
|
||||
`<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="5" cy="5" r="2" fill="#F7F8F8"/></svg>`
|
||||
);
|
||||
|
||||
export const LI = styled.li`
|
||||
color: ${getThemeColor('textLight')};
|
||||
list-style-image: url('data:image/svg+xml;utf8,${BulletPointSvg}');
|
||||
`;
|
||||
|
||||
export const Text = styled(Box)`
|
||||
font-size: ${(p) => p.theme.fontSizesPx[p.fontSize || 1]};
|
||||
line-height: 1.6;
|
||||
`;
|
||||
|
||||
export const TextLight = styled(Text)`
|
||||
color: ${getThemeColor('textLight')};
|
||||
`;
|
||||
|
||||
export const Label = styled(Box)`
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 1px;
|
||||
`;
|
||||
|
||||
export const AttributionText = styled(TextLight)`
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
font-size: ${(p) => p.theme.fontSizesPx[0]};
|
||||
`;
|
||||
|
||||
export const Paragraph = styled(Box)`
|
||||
max-width: 520px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 1.5;
|
||||
|
||||
code {
|
||||
color: ${(p) => p.theme.colors.violet};
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
export default ({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
connectionLineType,
|
||||
connectionLineStyle,
|
||||
}) => {
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#222"
|
||||
strokeWidth={1.5}
|
||||
className="animated"
|
||||
d={`M${sourceX},${sourceY} C ${sourceX} ${targetY} ${sourceX} ${targetY} ${targetX},${targetY}`}
|
||||
/>
|
||||
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
||||
</g>
|
||||
);
|
||||
};
|
||||
37
website/src/example-flows/CustomConnectionLine/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { useState } from 'react';
|
||||
import ReactFlow, {
|
||||
removeElements,
|
||||
addEdge,
|
||||
Background,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import ConnectionLine from './ConnectionLine';
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: 'connectionline-1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
];
|
||||
|
||||
const ConnectionLineFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onElementsRemove = (elementsToRemove) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
connectionLineComponent={ConnectionLine}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
>
|
||||
<Background variant="lines" />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionLineFlow;
|
||||
37
website/src/example-flows/CustomNode/ColorSelectorNode.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { Handle } from 'react-flow-renderer';
|
||||
|
||||
export default memo(({ data }) => {
|
||||
return (
|
||||
<>
|
||||
<Handle
|
||||
type="target"
|
||||
position="left"
|
||||
style={{ background: '#555' }}
|
||||
onConnect={(params) => console.log('handle onConnect', params)}
|
||||
/>
|
||||
<div>
|
||||
Custom Color Picker Node: <strong>{data.color}</strong>
|
||||
</div>
|
||||
<input
|
||||
className="nodrag"
|
||||
type="color"
|
||||
onChange={data.onChange}
|
||||
defaultValue={data.color}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position="right"
|
||||
id="a"
|
||||
style={{ top: 10, background: '#555' }}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position="right"
|
||||
id="b"
|
||||
style={{ bottom: 10, top: 'auto', background: '#555' }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
7
website/src/example-flows/CustomNode/index.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.react-flow__node-selectorNode {
|
||||
font-size: 12px;
|
||||
background: #eee;
|
||||
border: 1px solid #555;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
149
website/src/example-flows/CustomNode/index.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
isEdge,
|
||||
removeElements,
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import ColorSelectorNode from './ColorSelectorNode';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const onLoad = (reactFlowInstance) => {
|
||||
console.log('flow loaded:', reactFlowInstance);
|
||||
setTimeout(() => reactFlowInstance.fitView(), 1);
|
||||
};
|
||||
const onNodeDragStop = (event, node) => console.log('drag stop', node);
|
||||
const onElementClick = (event, element) => console.log('click', element);
|
||||
|
||||
const initBgColor = '#1A192B';
|
||||
|
||||
const connectionLineStyle = { stroke: '#fff' };
|
||||
const snapGrid = [20, 20];
|
||||
const nodeTypes = {
|
||||
selectorNode: ColorSelectorNode,
|
||||
};
|
||||
|
||||
const CustomNodeFlow = () => {
|
||||
const [elements, setElements] = useState([]);
|
||||
const [bgColor, setBgColor] = useState(initBgColor);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = (event) => {
|
||||
setElements((els) =>
|
||||
els.map((e) => {
|
||||
if (isEdge(e) || e.id !== '2') {
|
||||
return e;
|
||||
}
|
||||
|
||||
const color = event.target.value;
|
||||
|
||||
setBgColor(color);
|
||||
|
||||
return {
|
||||
...e,
|
||||
data: {
|
||||
...e.data,
|
||||
color,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
setElements([
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: { label: 'An input node' },
|
||||
position: { x: 0, y: 50 },
|
||||
sourcePosition: 'right',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'selectorNode',
|
||||
data: { onChange: onChange, color: initBgColor },
|
||||
style: { border: '1px solid #777', padding: 10 },
|
||||
position: { x: 300, y: 50 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'output',
|
||||
data: { label: 'Output A' },
|
||||
position: { x: 650, y: 25 },
|
||||
targetPosition: 'left',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'output',
|
||||
data: { label: 'Output B' },
|
||||
position: { x: 650, y: 100 },
|
||||
targetPosition: 'left',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'e1-2',
|
||||
source: '1',
|
||||
target: '2',
|
||||
animated: true,
|
||||
style: { stroke: '#fff' },
|
||||
},
|
||||
{
|
||||
id: 'e2a-3',
|
||||
source: '2__a',
|
||||
target: '3',
|
||||
animated: true,
|
||||
style: { stroke: '#fff' },
|
||||
},
|
||||
{
|
||||
id: 'e2b-4',
|
||||
source: '2__b',
|
||||
target: '4',
|
||||
animated: true,
|
||||
style: { stroke: '#fff' },
|
||||
},
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const onElementsRemove = (elementsToRemove) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) =>
|
||||
setElements((els) =>
|
||||
addEdge({ ...params, animated: true, style: { stroke: '#fff' } }, els)
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementClick={onElementClick}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
style={{ background: bgColor }}
|
||||
onLoad={onLoad}
|
||||
nodeTypes={nodeTypes}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
snapToGrid={true}
|
||||
snapGrid={snapGrid}
|
||||
defaultZoom={1.5}
|
||||
>
|
||||
<MiniMap
|
||||
nodeStrokeColor={(n) => {
|
||||
if (n.type === 'input') return '#0041d0';
|
||||
if (n.type === 'selectorNode') return bgColor;
|
||||
if (n.type === 'output') return '#ff0072';
|
||||
}}
|
||||
nodeColor={(n) => {
|
||||
if (n.type === 'selectorNode') return bgColor;
|
||||
return '#fff';
|
||||
}}
|
||||
/>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomNodeFlow;
|
||||
36
website/src/example-flows/EdgeTypes/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Example for checking the different edge types and source and target positions
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, { removeElements, addEdge, MiniMap, Controls, Background } from 'react-flow-renderer';
|
||||
import { getElements } from './utils';
|
||||
|
||||
const onLoad = (reactFlowInstance) => {
|
||||
reactFlowInstance.fitView();
|
||||
console.log(reactFlowInstance.getElements());
|
||||
};
|
||||
|
||||
const initialElements = getElements();
|
||||
|
||||
const EdgeTypesFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onLoad={onLoad}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
minZoom={0.2}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default EdgeTypesFlow;
|
||||
120
website/src/example-flows/EdgeTypes/utils.js
Normal file
@@ -0,0 +1,120 @@
|
||||
const nodeWidth = 80;
|
||||
const nodeGapWidth = nodeWidth * 2;
|
||||
const nodeStyle = { width: nodeWidth, fontSize: 11, color: 'white' };
|
||||
|
||||
const sourceTargetPositions = [
|
||||
{ source: 'bottom', target: 'top' },
|
||||
{ source: 'right', target: 'left' },
|
||||
];
|
||||
const nodeColors = [
|
||||
['#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'],
|
||||
['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8'],
|
||||
];
|
||||
const edgeTypes = ['default', 'step', 'smoothstep', 'straight'];
|
||||
const offsets = [
|
||||
{
|
||||
x: 0,
|
||||
y: -nodeGapWidth,
|
||||
},
|
||||
{
|
||||
x: nodeGapWidth,
|
||||
y: -nodeGapWidth,
|
||||
},
|
||||
{
|
||||
x: nodeGapWidth,
|
||||
y: 0,
|
||||
},
|
||||
{
|
||||
x: nodeGapWidth,
|
||||
y: nodeGapWidth,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: nodeGapWidth,
|
||||
},
|
||||
{
|
||||
x: -nodeGapWidth,
|
||||
y: nodeGapWidth,
|
||||
},
|
||||
{
|
||||
x: -nodeGapWidth,
|
||||
y: 0,
|
||||
},
|
||||
{
|
||||
x: -nodeGapWidth,
|
||||
y: -nodeGapWidth,
|
||||
},
|
||||
];
|
||||
|
||||
let id = 0;
|
||||
const getNodeId = () => `edgetypes-${(id++).toString()}`;
|
||||
|
||||
export function getElements() {
|
||||
const initialElements = [];
|
||||
|
||||
for (
|
||||
let sourceTargetIndex = 0;
|
||||
sourceTargetIndex < sourceTargetPositions.length;
|
||||
sourceTargetIndex++
|
||||
) {
|
||||
const currSourceTargetPos = sourceTargetPositions[sourceTargetIndex];
|
||||
|
||||
for (
|
||||
let edgeTypeIndex = 0;
|
||||
edgeTypeIndex < edgeTypes.length;
|
||||
edgeTypeIndex++
|
||||
) {
|
||||
const currEdgeType = edgeTypes[edgeTypeIndex];
|
||||
|
||||
for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
|
||||
const currOffset = offsets[offsetIndex];
|
||||
|
||||
const style = {
|
||||
...nodeStyle,
|
||||
background: nodeColors[sourceTargetIndex][edgeTypeIndex],
|
||||
};
|
||||
const sourcePosition = {
|
||||
x: offsetIndex * nodeWidth * 4,
|
||||
y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300,
|
||||
};
|
||||
const sourceId = getNodeId();
|
||||
const sourceData = { label: `Source ${sourceId}` };
|
||||
const sourceNode = {
|
||||
id: sourceId,
|
||||
style,
|
||||
data: sourceData,
|
||||
position: sourcePosition,
|
||||
sourcePosition: currSourceTargetPos.source,
|
||||
targetPosition: currSourceTargetPos.target,
|
||||
};
|
||||
|
||||
const targetId = getNodeId();
|
||||
const targetData = { label: `Target ${targetId}` };
|
||||
const targetPosition = {
|
||||
x: sourcePosition.x + currOffset.x,
|
||||
y: sourcePosition.y + currOffset.y,
|
||||
};
|
||||
const targetNode = {
|
||||
id: targetId,
|
||||
style,
|
||||
data: targetData,
|
||||
position: targetPosition,
|
||||
sourcePosition: currSourceTargetPos.source,
|
||||
targetPosition: currSourceTargetPos.target,
|
||||
};
|
||||
|
||||
initialElements.push(sourceNode);
|
||||
initialElements.push(targetNode);
|
||||
|
||||
initialElements.push({
|
||||
id: `${sourceId}-${targetId}`,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: currEdgeType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialElements;
|
||||
}
|
||||
30
website/src/example-flows/Edges/CustomEdge.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { getBezierPath, getMarkerEnd } from 'react-flow-renderer';
|
||||
|
||||
export default function CustomEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
style = {},
|
||||
data,
|
||||
arrowHeadType,
|
||||
markerEndId,
|
||||
}) {
|
||||
const edgePath = getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
|
||||
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<path id={id} style={style} className="react-flow__edge-path" d={edgePath} markerEnd={markerEnd} />
|
||||
<text>
|
||||
<textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle">
|
||||
{data.text}
|
||||
</textPath>
|
||||
</text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
152
website/src/example-flows/Edges/index.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
removeElements,
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import CustomEdge from './CustomEdge';
|
||||
|
||||
const onLoad = (reactFlowInstance) => reactFlowInstance.fitView();
|
||||
const onNodeDragStop = (event, node) => console.log('drag stop', node);
|
||||
const onElementClick = (event, element) => console.log('click', element);
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: 'edges-1',
|
||||
type: 'input',
|
||||
data: { label: 'Input 1' },
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
{ id: 'edges-2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
|
||||
{ id: 'edges-2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } },
|
||||
{ id: 'edges-3', data: { label: 'Node 3' }, position: { x: 250, y: 200 } },
|
||||
{ id: 'edges-4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } },
|
||||
{ id: 'edges-3a', data: { label: 'Node 3a' }, position: { x: 150, y: 300 } },
|
||||
{ id: 'edges-5', data: { label: 'Node 5' }, position: { x: 250, y: 400 } },
|
||||
{
|
||||
id: 'edges-6',
|
||||
type: 'output',
|
||||
data: { label: 'Output 6' },
|
||||
position: { x: 50, y: 550 },
|
||||
},
|
||||
{
|
||||
id: 'edges-7',
|
||||
type: 'output',
|
||||
data: { label: 'Output 7' },
|
||||
position: { x: 250, y: 550 },
|
||||
},
|
||||
{
|
||||
id: 'edges-8',
|
||||
type: 'output',
|
||||
data: { label: 'Output 8' },
|
||||
position: { x: 525, y: 600 },
|
||||
},
|
||||
{
|
||||
id: 'edges-e1-2',
|
||||
source: 'edges-1',
|
||||
target: 'edges-2',
|
||||
label: 'bezier edge (default)',
|
||||
className: 'normal-edge',
|
||||
},
|
||||
{
|
||||
id: 'edges-e2-2a',
|
||||
source: 'edges-2',
|
||||
target: 'edges-2a',
|
||||
type: 'smoothstep',
|
||||
label: 'smoothstep edge',
|
||||
},
|
||||
{
|
||||
id: 'edges-e2-3',
|
||||
source: 'edges-2',
|
||||
target: 'edges-3',
|
||||
type: 'step',
|
||||
label: 'step edge',
|
||||
},
|
||||
{
|
||||
id: 'edges-e3-4',
|
||||
source: 'edges-3',
|
||||
target: 'edges-4',
|
||||
type: 'straight',
|
||||
label: 'straight edge',
|
||||
},
|
||||
{
|
||||
id: 'edges-e3-3a',
|
||||
source: 'edges-3',
|
||||
target: 'edges-3a',
|
||||
type: 'straight',
|
||||
label: 'label only edge',
|
||||
style: { stroke: 'none' },
|
||||
},
|
||||
{
|
||||
id: 'edges-e3-5',
|
||||
source: 'edges-4',
|
||||
target: 'edges-5',
|
||||
animated: true,
|
||||
label: 'animated styled edge',
|
||||
style: { stroke: 'red' },
|
||||
},
|
||||
{
|
||||
id: 'edges-e5-6',
|
||||
source: 'edges-5',
|
||||
target: 'edges-6',
|
||||
label: 'styled label',
|
||||
labelStyle: { fill: 'red', fontWeight: 700 },
|
||||
arrowHeadType: 'arrow',
|
||||
},
|
||||
{
|
||||
id: 'edges-e5-7',
|
||||
source: 'edges-5',
|
||||
target: 'edges-7',
|
||||
label: 'label with styled bg',
|
||||
labelBgPadding: [8, 4],
|
||||
labelBgBorderRadius: 4,
|
||||
labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 },
|
||||
arrowHeadType: 'arrowclosed',
|
||||
},
|
||||
{
|
||||
id: 'edges-e5-8',
|
||||
source: 'edges-5',
|
||||
target: 'edges-8',
|
||||
type: 'custom',
|
||||
data: { text: 'custom edge' },
|
||||
arrowHeadType: 'arrowclosed',
|
||||
},
|
||||
];
|
||||
|
||||
const edgeTypes = {
|
||||
custom: CustomEdge,
|
||||
};
|
||||
|
||||
const EdgesFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
|
||||
const onElementsRemove = (elementsToRemove) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
|
||||
console.log('render edges');
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementClick={onElementClick}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onLoad={onLoad}
|
||||
snapToGrid={true}
|
||||
edgeTypes={edgeTypes}
|
||||
key="edges"
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default EdgesFlow;
|
||||
58
website/src/example-flows/Empty/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
removeElements,
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const onLoad = (reactFlowInstance) =>
|
||||
console.log('flow loaded:', reactFlowInstance);
|
||||
const onElementClick = (event, element) => console.log('click', element);
|
||||
const onNodeDragStop = (event, node) => console.log('drag stop', node);
|
||||
|
||||
const EmptyFlow = () => {
|
||||
const [elements, setElements] = useState([]);
|
||||
const onElementsRemove = (elementsToRemove) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
const addRandomNode = () => {
|
||||
const nodeId = `nodes-${(elements.length + 1).toString()}`;
|
||||
const newNode = {
|
||||
id: nodeId,
|
||||
data: { label: `Node: ${nodeId}` },
|
||||
position: {
|
||||
x: Math.random() * window.innerWidth,
|
||||
y: Math.random() * window.innerHeight,
|
||||
},
|
||||
};
|
||||
setElements((els) => els.concat(newNode));
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onLoad={onLoad}
|
||||
onElementClick={onElementClick}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={(p) => onConnect(p)}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background variant="lines" />
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={addRandomNode}
|
||||
style={{ position: 'absolute', left: 10, top: 10, zIndex: 4 }}
|
||||
>
|
||||
add node
|
||||
</button>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyFlow;
|
||||
58
website/src/example-flows/Hidden/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, { addEdge, MiniMap, Controls } from 'react-flow-renderer';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: 'hidden-1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
{ id: 'hidden-2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||
{ id: 'hidden-3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||
{ id: 'hidden-4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
||||
{ id: 'hidden-e1-2', source: 'hidden-1', target: 'hidden-2' },
|
||||
{ id: 'hidden-e1-3', source: 'hidden-1', target: 'hidden-3' },
|
||||
{ id: 'hidden-e3-4', source: 'hidden-3', target: 'hidden-4' },
|
||||
];
|
||||
|
||||
const HiddenFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const [isHidden, setIsHidden] = useState(false);
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
|
||||
useEffect(() => {
|
||||
setElements((els) =>
|
||||
els.map((e) => {
|
||||
e.isHidden = isHidden;
|
||||
return e;
|
||||
})
|
||||
);
|
||||
}, [isHidden]);
|
||||
|
||||
return (
|
||||
<ReactFlow elements={elements} onConnect={onConnect}>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
<div style={{ position: 'absolute', left: 10, top: 10, zIndex: 4 }}>
|
||||
<div>
|
||||
<label htmlFor="ishidden">
|
||||
isHidden
|
||||
<input
|
||||
id="ishidden"
|
||||
type="checkbox"
|
||||
checked={isHidden}
|
||||
onChange={(event) => setIsHidden(event.target.checked)}
|
||||
className="react-flow__ishidden"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default HiddenFlow;
|
||||
164
website/src/example-flows/Horizontal/index.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, { removeElements, addEdge } from 'react-flow-renderer';
|
||||
|
||||
const onLoad = (reactFlowInstance) => reactFlowInstance.fitView();
|
||||
|
||||
const onNodeMouseEnter = (event, node) => console.log('mouse enter:', node);
|
||||
const onNodeMouseMove = (event, node) => console.log('mouse move:', node);
|
||||
const onNodeMouseLeave = (event, node) => console.log('mouse leave:', node);
|
||||
const onNodeContextMenu = (event, node) => {
|
||||
event.preventDefault();
|
||||
console.log('context menu:', node);
|
||||
};
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: 'horizontal-1',
|
||||
sourcePosition: 'right',
|
||||
type: 'input',
|
||||
className: 'dark-node',
|
||||
data: { label: 'Input' },
|
||||
position: { x: 0, y: 80 },
|
||||
},
|
||||
{
|
||||
id: 'horizontal-2',
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
data: { label: 'A Node' },
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
{
|
||||
id: 'horizontal-3',
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 250, y: 160 },
|
||||
},
|
||||
{
|
||||
id: 'horizontal-4',
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 500, y: 0 },
|
||||
},
|
||||
{
|
||||
id: 'horizontal-5',
|
||||
sourcePosition: 'top',
|
||||
targetPosition: 'bottom',
|
||||
data: { label: 'Node 5' },
|
||||
position: { x: 500, y: 100 },
|
||||
},
|
||||
{
|
||||
id: 'horizontal-6',
|
||||
sourcePosition: 'bottom',
|
||||
targetPosition: 'top',
|
||||
data: { label: 'Node 6' },
|
||||
position: { x: 500, y: 230 },
|
||||
},
|
||||
{
|
||||
id: 'horizontal-7',
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
data: { label: 'Node 7' },
|
||||
position: { x: 750, y: 50 },
|
||||
},
|
||||
{
|
||||
id: 'horizontal-8',
|
||||
sourcePosition: 'right',
|
||||
targetPosition: 'left',
|
||||
data: { label: 'Node 8' },
|
||||
position: { x: 750, y: 300 },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'horizontal-e1-2',
|
||||
source: 'horizontal-1',
|
||||
type: 'smoothstep',
|
||||
target: 'horizontal-2',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'horizontal-e1-3',
|
||||
source: 'horizontal-1',
|
||||
type: 'smoothstep',
|
||||
target: 'horizontal-3',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'horizontal-e1-4',
|
||||
source: 'horizontal-2',
|
||||
type: 'smoothstep',
|
||||
target: 'horizontal-4',
|
||||
label: 'edge label',
|
||||
},
|
||||
{
|
||||
id: 'horizontal-e3-5',
|
||||
source: 'horizontal-3',
|
||||
type: 'smoothstep',
|
||||
target: 'horizontal-5',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'horizontal-e3-6',
|
||||
source: 'horizontal-3',
|
||||
type: 'smoothstep',
|
||||
target: 'horizontal-6',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'horizontal-e5-7',
|
||||
source: 'horizontal-5',
|
||||
type: 'smoothstep',
|
||||
target: 'horizontal-7',
|
||||
animated: true,
|
||||
},
|
||||
{
|
||||
id: 'horizontal-e6-8',
|
||||
source: 'horizontal-6',
|
||||
type: 'smoothstep',
|
||||
target: 'horizontal-8',
|
||||
animated: true,
|
||||
},
|
||||
];
|
||||
|
||||
const HorizontalFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onElementsRemove = (elementsToRemove) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
const changeClassName = () => {
|
||||
setElements((elms) =>
|
||||
elms.map((el) => {
|
||||
if (el.type === 'input') {
|
||||
el.className = el.className ? '' : 'dark-node';
|
||||
}
|
||||
|
||||
return { ...el };
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
onLoad={onLoad}
|
||||
selectNodesOnDrag={false}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
onNodeMouseMove={onNodeMouseMove}
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
>
|
||||
<button
|
||||
onClick={changeClassName}
|
||||
style={{ position: 'absolute', right: 10, top: 30, zIndex: 4 }}
|
||||
>
|
||||
change class name
|
||||
</button>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalFlow;
|
||||
194
website/src/example-flows/Interaction/index.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, { addEdge, MiniMap, Controls } from 'react-flow-renderer';
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: 'interaction-1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
{
|
||||
id: 'interaction-2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: 'interaction-3',
|
||||
data: { label: 'Node 3' },
|
||||
position: { x: 400, y: 100 },
|
||||
},
|
||||
{
|
||||
id: 'interaction-4',
|
||||
data: { label: 'Node 4' },
|
||||
position: { x: 400, y: 200 },
|
||||
},
|
||||
{
|
||||
id: 'interaction-e1-2',
|
||||
source: 'interaction-1',
|
||||
target: 'interaction-2',
|
||||
animated: true,
|
||||
},
|
||||
{ id: 'interaction-e1-3', source: 'interaction-1', target: 'interaction-3' },
|
||||
];
|
||||
|
||||
const onNodeDragStart = (event, node) => console.log('drag start', node);
|
||||
const onNodeDragStop = (event, node) => console.log('drag stop', node);
|
||||
const onElementClick = (event, element) => console.log('click', element);
|
||||
const onPaneClick = (event) => console.log('onPaneClick', event);
|
||||
const onPaneScroll = (event) => console.log('onPaneScroll', event);
|
||||
const onPaneContextMenu = (event) => console.log('onPaneContextMenu', event);
|
||||
const onLoad = (reactFlowInstance) =>
|
||||
reactFlowInstance.fitView({ padding: 0.2 });
|
||||
|
||||
const InteractionFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
|
||||
const [isSelectable, setIsSelectable] = useState(false);
|
||||
const [isDraggable, setIsDraggable] = useState(false);
|
||||
const [isConnectable, setIsConnectable] = useState(false);
|
||||
const [zoomOnScroll, setZoomOnScroll] = useState(false);
|
||||
const [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(false);
|
||||
const [paneMoveable, setPaneMoveable] = useState(true);
|
||||
const [captureZoomClick, setCaptureZoomClick] = useState(false);
|
||||
const [captureZoomScroll, setCaptureZoomScroll] = useState(false);
|
||||
const [captureElementClick, setCaptureElementClick] = useState(false);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
elementsSelectable={isSelectable}
|
||||
nodesConnectable={isConnectable}
|
||||
nodesDraggable={isDraggable}
|
||||
zoomOnScroll={zoomOnScroll}
|
||||
zoomOnDoubleClick={zoomOnDoubleClick}
|
||||
onConnect={onConnect}
|
||||
onElementClick={captureElementClick ? onElementClick : undefined}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
paneMoveable={paneMoveable}
|
||||
onPaneClick={captureZoomClick ? onPaneClick : undefined}
|
||||
onPaneScroll={captureZoomScroll ? onPaneScroll : undefined}
|
||||
onPaneContextMenu={captureZoomClick ? onPaneContextMenu : undefined}
|
||||
onLoad={onLoad}
|
||||
>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
|
||||
<div style={{ position: 'absolute', left: 10, top: 10, zIndex: 4 }}>
|
||||
<div>
|
||||
<label htmlFor="draggable">
|
||||
draggable
|
||||
<input
|
||||
id="draggable"
|
||||
type="checkbox"
|
||||
checked={isDraggable}
|
||||
onChange={(event) => setIsDraggable(event.target.checked)}
|
||||
className="react-flow__draggable"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="connectable">
|
||||
connectable
|
||||
<input
|
||||
id="connectable"
|
||||
type="checkbox"
|
||||
checked={isConnectable}
|
||||
onChange={(event) => setIsConnectable(event.target.checked)}
|
||||
className="react-flow__connectable"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="selectable">
|
||||
selectable
|
||||
<input
|
||||
id="selectable"
|
||||
type="checkbox"
|
||||
checked={isSelectable}
|
||||
onChange={(event) => setIsSelectable(event.target.checked)}
|
||||
className="react-flow__selectable"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="zoomonscroll">
|
||||
zoom on scroll
|
||||
<input
|
||||
id="zoomonscroll"
|
||||
type="checkbox"
|
||||
checked={zoomOnScroll}
|
||||
onChange={(event) => setZoomOnScroll(event.target.checked)}
|
||||
className="react-flow__zoomonscroll"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="zoomondbl">
|
||||
zoom on double click
|
||||
<input
|
||||
id="zoomondbl"
|
||||
type="checkbox"
|
||||
checked={zoomOnDoubleClick}
|
||||
onChange={(event) => setZoomOnDoubleClick(event.target.checked)}
|
||||
className="react-flow__zoomondbl"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="panemoveable">
|
||||
pane moveable
|
||||
<input
|
||||
id="panemoveable"
|
||||
type="checkbox"
|
||||
checked={paneMoveable}
|
||||
onChange={(event) => setPaneMoveable(event.target.checked)}
|
||||
className="react-flow__panemoveable"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="capturezoompaneclick">
|
||||
capture zoom pane click
|
||||
<input
|
||||
id="capturezoompaneclick"
|
||||
type="checkbox"
|
||||
checked={captureZoomClick}
|
||||
onChange={(event) => setCaptureZoomClick(event.target.checked)}
|
||||
className="react-flow__capturezoompaneclick"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="capturezoompanescroll">
|
||||
capture zoom pane scroll
|
||||
<input
|
||||
id="capturezoompanescroll"
|
||||
type="checkbox"
|
||||
checked={captureZoomScroll}
|
||||
onChange={(event) => setCaptureZoomScroll(event.target.checked)}
|
||||
className="react-flow__capturezoompanescroll"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="captureelementclick">
|
||||
capture element click
|
||||
<input
|
||||
id="captureelementclick"
|
||||
type="checkbox"
|
||||
checked={captureElementClick}
|
||||
onChange={(event) => setCaptureElementClick(event.target.checked)}
|
||||
className="react-flow__captureelementclick"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default InteractionFlow;
|
||||
192
website/src/example-flows/Overview/index.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, {
|
||||
removeElements,
|
||||
addEdge,
|
||||
MiniMap,
|
||||
Controls,
|
||||
Background,
|
||||
isNode,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
const onNodeDragStart = (event, node) => console.log('drag start', node);
|
||||
const onNodeDragStop = (event, node) => console.log('drag stop', node);
|
||||
const onSelectionDrag = (event, nodes) => console.log('selection drag', nodes);
|
||||
const onSelectionDragStart = (event, nodes) =>
|
||||
console.log('selection drag start', nodes);
|
||||
const onSelectionDragStop = (event, nodes) =>
|
||||
console.log('selection drag stop', nodes);
|
||||
const onSelectionContextMenu = (event, nodes) => {
|
||||
event.preventDefault();
|
||||
console.log('selection context menu', nodes);
|
||||
};
|
||||
const onElementClick = (event, element) =>
|
||||
console.log(`${isNode(element) ? 'node' : 'edge'} click:`, element);
|
||||
const onSelectionChange = (elements) =>
|
||||
console.log('selection change', elements);
|
||||
const onLoad = (reactFlowInstance) => {
|
||||
console.log('flow loaded:', reactFlowInstance);
|
||||
reactFlowInstance.fitView();
|
||||
};
|
||||
|
||||
const onMoveEnd = (transform) => console.log('zoom/move end', transform);
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
Welcome to <strong>React Flow!</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 250, y: 0 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
This is a <strong>default node</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
This one has a <strong>custom style</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 400, y: 100 },
|
||||
style: {
|
||||
background: '#D6D5E6',
|
||||
color: '#333',
|
||||
border: '1px solid #222138',
|
||||
width: 180,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
position: { x: 250, y: 200 },
|
||||
data: {
|
||||
label: 'Another default node',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
data: {
|
||||
label: 'Node id: 5',
|
||||
},
|
||||
position: { x: 250, y: 325 },
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'output',
|
||||
data: {
|
||||
label: (
|
||||
<>
|
||||
An <strong>output node</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
position: { x: 100, y: 480 },
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'output',
|
||||
data: { label: 'Another output node' },
|
||||
position: { x: 400, y: 450 },
|
||||
},
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'this is an edge label' },
|
||||
{ id: 'e1-3', source: '1', target: '3' },
|
||||
{
|
||||
id: 'e3-4',
|
||||
source: '3',
|
||||
target: '4',
|
||||
animated: true,
|
||||
label: 'animated edge',
|
||||
},
|
||||
{
|
||||
id: 'e4-5',
|
||||
source: '4',
|
||||
target: '5',
|
||||
arrowHeadType: 'arrowclosed',
|
||||
label: 'edge with arrow head',
|
||||
},
|
||||
{
|
||||
id: 'e5-6',
|
||||
source: '5',
|
||||
target: '6',
|
||||
type: 'smoothstep',
|
||||
label: 'smooth step edge',
|
||||
},
|
||||
{
|
||||
id: 'e5-7',
|
||||
source: '5',
|
||||
target: '7',
|
||||
type: 'step',
|
||||
style: { stroke: '#f6ab6c' },
|
||||
label: 'a step edge',
|
||||
animated: true,
|
||||
labelStyle: { fill: '#f6ab6c', fontWeight: 700 },
|
||||
},
|
||||
];
|
||||
|
||||
const connectionLineStyle = { stroke: '#ddd' };
|
||||
const snapGrid = [16, 16];
|
||||
|
||||
const OverviewFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onElementsRemove = (elementsToRemove) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementClick={onElementClick}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onSelectionDragStart={onSelectionDragStart}
|
||||
onSelectionDrag={onSelectionDrag}
|
||||
onSelectionDragStop={onSelectionDragStop}
|
||||
onSelectionContextMenu={onSelectionContextMenu}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onMoveEnd={onMoveEnd}
|
||||
onLoad={onLoad}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
snapToGrid={true}
|
||||
snapGrid={snapGrid}
|
||||
>
|
||||
<MiniMap
|
||||
nodeStrokeColor={(n) => {
|
||||
if (n.style?.background) return n.style.background;
|
||||
if (n.type === 'input') return '#0041d0';
|
||||
if (n.type === 'output') return '#ff0072';
|
||||
if (n.type === 'default') return '#1a192b';
|
||||
|
||||
return '#eee';
|
||||
}}
|
||||
nodeColor={(n) => {
|
||||
if (n.style?.background) return n.style.background;
|
||||
|
||||
return '#fff';
|
||||
}}
|
||||
borderRadius={2}
|
||||
/>
|
||||
<Controls />
|
||||
<Background color="#aaa" gap={16} />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewFlow;
|
||||
34
website/src/example-flows/Provider/Sidebar.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { useStoreState, useStoreActions } from 'react-flow-renderer';
|
||||
|
||||
export default () => {
|
||||
const nodes = useStoreState((store) => store.nodes);
|
||||
const transform = useStoreState((store) => store.transform);
|
||||
const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements);
|
||||
|
||||
const selectAll = () => {
|
||||
setSelectedElements(nodes.map((node) => ({ id: node.id, type: node.type })));
|
||||
};
|
||||
|
||||
return (
|
||||
<aside>
|
||||
<div className="description">
|
||||
This is an example of how you can access the internal state outside of the ReactFlow component.
|
||||
</div>
|
||||
<div className="title">Zoom & pan transform</div>
|
||||
<div className="transform">
|
||||
[{transform[0].toFixed(2)}, {transform[1].toFixed(2)}, {transform[2].toFixed(2)}]
|
||||
</div>
|
||||
<div className="title">Nodes</div>
|
||||
{nodes.map((node) => (
|
||||
<div key={node.id}>
|
||||
Node {node.id} - x: {node.__rf.position.x.toFixed(2)}, y: {node.__rf.position.y.toFixed(2)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="selectall">
|
||||
<button onClick={selectAll}>select all nodes</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
62
website/src/example-flows/Provider/index.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { useState } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
addEdge,
|
||||
removeElements,
|
||||
Controls,
|
||||
} from 'react-flow-renderer';
|
||||
|
||||
import Sidebar from './Sidebar';
|
||||
|
||||
import './provider.css';
|
||||
|
||||
const onElementClick = (event, element) => console.log('click', element);
|
||||
const onLoad = (reactFlowInstance) =>
|
||||
console.log('flow loaded:', reactFlowInstance);
|
||||
|
||||
const initialElements = [
|
||||
{
|
||||
id: 'provider-1',
|
||||
type: 'input',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
{ id: 'provider-2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
|
||||
{ id: 'provider-3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
|
||||
{ id: 'provider-4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
|
||||
{
|
||||
id: 'provider-e1-2',
|
||||
source: 'provider-1',
|
||||
target: 'provider-2',
|
||||
animated: true,
|
||||
},
|
||||
{ id: 'provider-e1-3', source: 'provider-1', target: 'provider-3' },
|
||||
];
|
||||
|
||||
const ProviderFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
const onElementsRemove = (elementsToRemove) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
|
||||
return (
|
||||
<div className="providerflow">
|
||||
<ReactFlowProvider>
|
||||
<div className="reactflow-wrapper">
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onElementClick={onElementClick}
|
||||
onConnect={onConnect}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onLoad={onLoad}
|
||||
>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
<Sidebar />
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProviderFlow;
|
||||
46
website/src/example-flows/Provider/provider.css
Normal file
@@ -0,0 +1,46 @@
|
||||
.providerflow {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.providerflow aside {
|
||||
border-left: 1px solid #eee;
|
||||
padding: 15px 10px;
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.providerflow aside .description {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.providerflow aside .title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.providerflow aside .transform {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.providerflow .reactflow-wrapper {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.providerflow .selectall {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.providerflow {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.providerflow aside {
|
||||
width: 20%;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
49
website/src/example-flows/Stress/index.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, { removeElements, addEdge, MiniMap, isNode, Controls, Background } from 'react-flow-renderer';
|
||||
import { getElements } from './utils';
|
||||
|
||||
const onLoad = (reactFlowInstance) => {
|
||||
reactFlowInstance.fitView();
|
||||
console.log(reactFlowInstance.getElements());
|
||||
};
|
||||
|
||||
const initialElements = getElements(10, 10);
|
||||
|
||||
const StressFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params) => setElements((els) => addEdge(params, els));
|
||||
|
||||
const updatePos = () => {
|
||||
setElements((elms) => {
|
||||
return elms.map((el) => {
|
||||
if (isNode(el)) {
|
||||
return {
|
||||
...el,
|
||||
position: {
|
||||
x: Math.random() * window.innerWidth,
|
||||
y: Math.random() * window.innerHeight,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return el;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow elements={elements} onLoad={onLoad} onElementsRemove={onElementsRemove} onConnect={onConnect}>
|
||||
<MiniMap />
|
||||
<Controls />
|
||||
<Background />
|
||||
|
||||
<button onClick={updatePos} style={{ position: 'absolute', right: 10, top: 30, zIndex: 4 }}>
|
||||
change pos
|
||||
</button>
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default StressFlow;
|
||||
32
website/src/example-flows/Stress/utils.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export function getElements(xElements = 10, yElements = 10) {
|
||||
const initialElements = [];
|
||||
let nodeId = 1;
|
||||
let recentNodeId = null;
|
||||
|
||||
for (let y = 0; y < yElements; y++) {
|
||||
for (let x = 0; x < xElements; x++) {
|
||||
const position = { x: x * 100, y: y * 50 };
|
||||
const data = { label: `Node ${nodeId}` };
|
||||
const node = {
|
||||
id: `stress-${nodeId.toString()}`,
|
||||
style: { width: 50, fontSize: 11 },
|
||||
data,
|
||||
position,
|
||||
};
|
||||
initialElements.push(node);
|
||||
|
||||
if (recentNodeId && nodeId <= xElements * yElements) {
|
||||
initialElements.push({
|
||||
id: `${x}-${y}`,
|
||||
source: `stress-${recentNodeId.toString()}`,
|
||||
target: `stress-${nodeId.toString()}`,
|
||||
});
|
||||
}
|
||||
|
||||
recentNodeId = nodeId;
|
||||
nodeId++;
|
||||
}
|
||||
}
|
||||
|
||||
return initialElements;
|
||||
}
|
||||
62
website/src/example-flows/Validation/index.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import ReactFlow, { addEdge, Handle } from 'react-flow-renderer';
|
||||
|
||||
import './validation.css';
|
||||
|
||||
const initialElements = [
|
||||
{ id: '0', type: 'custominput', position: { x: 0, y: 150 } },
|
||||
{ id: 'A', type: 'customnode', position: { x: 250, y: 0 } },
|
||||
{ id: 'B', type: 'customnode', position: { x: 250, y: 150 } },
|
||||
{ id: 'C', type: 'customnode', position: { x: 250, y: 300 } },
|
||||
];
|
||||
|
||||
const onLoad = (reactFlowInstance) => reactFlowInstance.fitView();
|
||||
const isValidConnection = (connection) => connection.target === 'B';
|
||||
const onConnectStart = (event, { nodeId, handleType }) => console.log('on connect start', { nodeId, handleType });
|
||||
const onConnectStop = (event) => console.log('on connect stop', event);
|
||||
const onConnectEnd = (event) => console.log('on connect end', event);
|
||||
|
||||
const CustomInput = () => (
|
||||
<>
|
||||
<div>Only connectable with B</div>
|
||||
<Handle type="source" position="right" isValidConnection={isValidConnection} />
|
||||
</>
|
||||
);
|
||||
|
||||
const CustomNode = ({ id }) => (
|
||||
<>
|
||||
<Handle type="target" position="left" isValidConnection={isValidConnection} />
|
||||
<div>{id}</div>
|
||||
<Handle type="source" position="right" isValidConnection={isValidConnection} />
|
||||
</>
|
||||
);
|
||||
|
||||
const nodeTypes = {
|
||||
custominput: CustomInput,
|
||||
customnode: CustomNode,
|
||||
};
|
||||
|
||||
const HorizontalFlow = () => {
|
||||
const [elements, setElements] = useState(initialElements);
|
||||
const onConnect = (params) => {
|
||||
console.log('on connect', params);
|
||||
setElements((els) => addEdge(params, els));
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
elements={elements}
|
||||
onConnect={onConnect}
|
||||
selectNodesOnDrag={false}
|
||||
onLoad={onLoad}
|
||||
className="validationflow"
|
||||
nodeTypes={nodeTypes}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectStop={onConnectStop}
|
||||
onConnectEnd={onConnectEnd}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalFlow;
|
||||
31
website/src/example-flows/Validation/validation.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.validationflow .react-flow__node {
|
||||
width: 150px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
color: #555;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__node-customnode {
|
||||
background: #e6e6e9;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.react-flow__node-custominput .react-flow__handle {
|
||||
background: #e6e6e9;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__node-custominput {
|
||||
background: #fff;
|
||||
|
||||
}
|
||||
|
||||
.validationflow .react-flow__handle-connecting {
|
||||
background: #ff6060;
|
||||
}
|
||||
|
||||
.validationflow .react-flow__handle-valid {
|
||||
background: #55dd99;
|
||||
}
|
||||
33
website/src/hooks/useExamplePages.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
|
||||
export default function useFeaturedProjects() {
|
||||
const rawData = useStaticQuery(graphql`
|
||||
query ExamplePages {
|
||||
allJavascriptFrontmatter {
|
||||
edges {
|
||||
node {
|
||||
frontmatter {
|
||||
title
|
||||
slug
|
||||
order
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const data = useMemo(() => {
|
||||
return rawData.allJavascriptFrontmatter.edges
|
||||
.sort((a, b) => {
|
||||
return a.node.frontmatter.order - b.node.frontmatter.order;
|
||||
})
|
||||
.map(({ node }) => ({
|
||||
slug: `/examples/${node.frontmatter.slug}`,
|
||||
title: node.frontmatter.title,
|
||||
}));
|
||||
}, [rawData]);
|
||||
|
||||
return data;
|
||||
}
|
||||
23
website/src/hooks/useMenuHeight.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const getInnerHeight = () => {
|
||||
return typeof window !== 'undefined' ? window.innerHeight : 0;
|
||||
};
|
||||
|
||||
export default function useMenuHeight() {
|
||||
const [menuHeight, setMenuHeight] = useState(getInnerHeight());
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
setMenuHeight(getInnerHeight());
|
||||
};
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
});
|
||||
|
||||
return menuHeight;
|
||||
}
|
||||
31
website/src/hooks/useShowcaseImages.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useStaticQuery, graphql } from 'gatsby';
|
||||
|
||||
export default function useFeaturedProjects() {
|
||||
const rawData = useStaticQuery(graphql`
|
||||
query ShowcaseImages {
|
||||
allShowcasesJson {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
url
|
||||
image {
|
||||
childImageSharp {
|
||||
fluid(maxWidth: 550, quality: 80, toFormat: JPG) {
|
||||
...GatsbyImageSharpFluid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const images = useMemo(
|
||||
() => rawData.allShowcasesJson.edges.map(({ node }) => node),
|
||||
[rawData]
|
||||
);
|
||||
|
||||
return images;
|
||||
}
|
||||
73
website/src/markdown/docs/api/component-props.md
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Prop Types
|
||||
---
|
||||
|
||||
This is the list of prop types you can pass to the main `ReactFlow` component.
|
||||
|
||||
```jsx
|
||||
import ReactFlow from 'react-flow-renderer';
|
||||
```
|
||||
|
||||
### Basic Props
|
||||
- `elements`: array of [nodes](/docs/api/nodes/) and [edges](/docs/api/edges/) *(required)*
|
||||
- `style`: css properties
|
||||
- `className`: additional class name
|
||||
|
||||
### Flow View
|
||||
- `minZoom`: default: `0.5`
|
||||
- `maxZoom`: default: `2`
|
||||
- `defaultZoom`: default: `1`
|
||||
- `defaultPosition`: default: `[0, 0]`
|
||||
- `snapToGrid`: default: `false`
|
||||
- `snapGrid`: [x, y] array - default: `[15, 15]`
|
||||
- `onlyRenderVisibleNodes`: default: `true`
|
||||
- `translateExtent`: [default `[[-∞, -∞], [+∞, +∞]]`](https://github.com/d3/d3-zoom#zoom_translateExtent)
|
||||
|
||||
### Event Handlers
|
||||
- `onElementClick(event, element)`: called when user clicks node or edge
|
||||
- `onElementsRemove(elements)`: called when user removes node or edge
|
||||
- `onNodeDragStart(event, node)`: node drag start
|
||||
- `onNodeDragStop(event, node)`: node drag stop
|
||||
- `onNodeMouseEnter(event, node)`: node mouse enter
|
||||
- `onNodeMouseMove(event, node)`: node mouse move
|
||||
- `onNodeMouseLeave(event, node)`: node mouse leave
|
||||
- `onNodeContextMenu(event, node)`: node context menu
|
||||
- `onConnect({ source, target })`: called when user connects two nodes
|
||||
- `onConnectStart(event, { nodeId, handleType })`: called when user starts to drag connection line
|
||||
- `onConnectStop(event)`: called when user stops to drag connection line
|
||||
- `onConnectEnd(event)`: called after user stops or connects nodes
|
||||
- `onLoad(reactFlowInstance)`: called after flow is initialized
|
||||
- `onMove(flowTransform)`: called when user is panning or zooming
|
||||
- `onMoveStart(flowTransform)`: called when user starts panning or zooming
|
||||
- `onMoveEnd(flowTransform)`: called when user ends panning or zooming
|
||||
- `onSelectionChange(elements)`: called when user selects one or multiple elements
|
||||
- `onSelectionDragStart(event, nodes)`: called when user starts to drag a selection
|
||||
- `onSelectionDrag(event, nodes)`: called when user drags a selection
|
||||
- `onSelectionDragStop(event, nodes)`: called when user stops to drag a selection
|
||||
- `onSelectionContextMenu(event, nodes)`: called when user does a right-click on a selection
|
||||
- `onPaneClick(event)`: called when user clicks directly on the canvas
|
||||
- `onPaneContextMenu(event)`: called when user does a right-click on the canvas
|
||||
- `onPaneScroll(event)`: called when user scrolls pane (only works when `zoomOnScroll` is set to `false)
|
||||
|
||||
### Interaction
|
||||
- `nodesDraggable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `draggable` node option
|
||||
- `nodesConnectable`: default: `true`. This applies to all nodes. You can also change the behavior of a specific node with the `connectable` node option
|
||||
- `elementsSelectable`: default: `true`. This applies to all elements. You can also change the behavior of a specific node with the `selectable` node option
|
||||
- `zoomOnScroll`: default: `true`
|
||||
- `zoomOnDoubleClick`: default: `true`
|
||||
- `selectNodesOnDrag`: default: `true`
|
||||
- `paneMoveable`: default: `true` - If set to `false`, panning and zooming is disabled
|
||||
|
||||
### Element Customization
|
||||
- `nodeTypes`: object with [node types](#node-types--custom-nodes)
|
||||
- `edgeTypes`: object with [edge types](#edge-types--custom-edges)
|
||||
- `arrowHeadColor`: default: `#b1b1b7`
|
||||
|
||||
### Connection Line Options
|
||||
- `connectionLineType`: connection line type = `default` (bezier), `straight`, `step`, `smoothstep`
|
||||
- `connectionLineStyle`: connection style as svg attributes
|
||||
- `connectionLineComponent`: [custom connection line component](/example/src/CustomConnectionLine/index.js)
|
||||
|
||||
### Keys
|
||||
- `deleteKeyCode`: default: `8` (delete)
|
||||
- `selectionKeyCode`: default: `16` (shift)
|
||||
31
website/src/markdown/docs/api/components/background.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Background
|
||||
---
|
||||
|
||||
React Flow comes with two background variants: **dots** and **lines**. You can use it by passing it as a children to the `ReactFlow` component:
|
||||
|
||||
### Usage
|
||||
|
||||
```jsx
|
||||
import ReactFlow, { Background } from 'react-flow-renderer';
|
||||
|
||||
const FlowWithBackground = () => (
|
||||
<ReactFlow elements={elements}>
|
||||
<Background
|
||||
variant="dots"
|
||||
gap={12}
|
||||
size={4}
|
||||
/>
|
||||
</ReactFlow>
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
### Prop Types
|
||||
|
||||
- `variant`: string - has to be 'dots' or 'lines' - default: `dots`
|
||||
- `gap`: number - the gap between the dots or lines - default: `16`
|
||||
- `size`: number - the radius of the dots or the stroke width of the lines - default: `0.5`
|
||||
- `color`: string - the color of the dots or lines - default: `#81818a` for dots, `#eee` for lines
|
||||
- `style`: css properties
|
||||
- `className`: additional class name
|
||||
25
website/src/markdown/docs/api/components/controls.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Controls
|
||||
---
|
||||
|
||||
The control panel contains a zoom-in, zoom-out, fit-view and a lock/unlock button. You can use it by passing it as a children to the `ReactFlow` component:
|
||||
|
||||
### Usage
|
||||
|
||||
```jsx
|
||||
import ReactFlow, { Controls } from 'react-flow-renderer';
|
||||
|
||||
const FlowWithControls = () => (
|
||||
<ReactFlow elements={elements}>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
);
|
||||
```
|
||||
|
||||
### Prop Types
|
||||
|
||||
- `showZoom`: boolean - default: true
|
||||
- `showFitView`: boolean - default: true
|
||||
- `showInteractive`: boolean - default: true
|
||||
- `style`: css properties
|
||||
- `className`: additional class name
|
||||