feat(website): init
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&: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>
|
||||
);
|
||||
@@ -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)};
|
||||
`;
|
||||
@@ -0,0 +1,40 @@
|
||||
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: '20px',
|
||||
borderRadius: '6px',
|
||||
margin: '40px 0 40px 0',
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{tokens
|
||||
.filter((line) => line.some((l) => l.content))
|
||||
.map((line, i) => (
|
||||
<div key={i} {...getLineProps({ line, key: i })}>
|
||||
{line.map((token, key) => (
|
||||
<span key={key} {...getTokenProps({ token, key })} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Box } from 'reflexbox';
|
||||
|
||||
import { colors } from 'themes';
|
||||
import CodeBlock from 'components/Mdx/CodeBlock';
|
||||
import { H4, AttributionText } from 'components/Typo';
|
||||
|
||||
const Wrapper = styled(Box)`
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Flex } from 'reflexbox';
|
||||
import { Link } from 'gatsby';
|
||||
|
||||
import Button from 'components/Button';
|
||||
import { H3 } from 'components/Typo';
|
||||
import { getThemeSpacePx } from 'utils/css-utils';
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
text-align: center;
|
||||
|
||||
${H3} {
|
||||
margin-bottom: ${getThemeSpacePx(3)};
|
||||
}
|
||||
`;
|
||||
|
||||
export default ({
|
||||
caption = 'Are you interested in a collaboration?',
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Wrapper alignItems="center" px={2} flexDirection="column" {...props}>
|
||||
<H3 as="div">{caption}</H3>
|
||||
<Button as={Link} to="/contact" icon="mail">
|
||||
Contact us
|
||||
</Button>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
@@ -0,0 +1,252 @@
|
||||
import React, { useEffect, 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 { getThemeColor, getThemeSpacePx, device, px } from 'utils/css-utils';
|
||||
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: 100;
|
||||
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`
|
||||
@media ${device.tablet} {
|
||||
margin-left: 50px;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
background: ${(p) => p.theme.colors.textDark};
|
||||
|
||||
&&& {
|
||||
&:hover {
|
||||
background: ${getThemeColor('red')};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Close = styled.div`
|
||||
padding: ${getThemeSpacePx(1)};
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
`;
|
||||
|
||||
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 [menuHeight, setMenuHeight] = useState(
|
||||
typeof window !== 'undefined' ? window.innerHeight : 0
|
||||
);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const toggleMenu = () => {
|
||||
const nextMenuOpen = !menuOpen;
|
||||
|
||||
document.body.classList.toggle('noscroll', nextMenuOpen);
|
||||
|
||||
setMenuOpen(nextMenuOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
setMenuHeight(typeof window !== 'undefined' ? window.innerHeight : 0);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
});
|
||||
|
||||
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">
|
||||
Docs
|
||||
</NavItem>
|
||||
<NavItem to="/examples" activeClassName="active">
|
||||
Examples
|
||||
</NavItem>
|
||||
|
||||
<Button
|
||||
as={GithubButton}
|
||||
href="https://github.com/wbkd/react-flow"
|
||||
activeClassName="active"
|
||||
icon="github_circle"
|
||||
className="desktop"
|
||||
isButton
|
||||
color="textInverted"
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
|
||||
<a
|
||||
href="https://github.com/wbkd/react-flow"
|
||||
activeClassName="active"
|
||||
className="mobile"
|
||||
isButton
|
||||
>
|
||||
Github
|
||||
</a>
|
||||
</NavInner>
|
||||
</Nav>
|
||||
<Flex pb={4} alignItems="center" className="mobile">
|
||||
<Close onClick={toggleMenu}>
|
||||
<Icon name="close" strokeColor="text" colorizeStroke />
|
||||
</Close>
|
||||
</Flex>
|
||||
</NavWrapper>
|
||||
</Centered>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,73 @@
|
||||
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')};
|
||||
font-size: 12px;
|
||||
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;
|
||||
@@ -0,0 +1,134 @@
|
||||
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 getOffset = () => {
|
||||
if (windowWidth < 800) {
|
||||
return 0;
|
||||
} else if (windowWidth < 1200) {
|
||||
return 405;
|
||||
} else {
|
||||
return (windowWidth - 1200) / 2 + 405;
|
||||
}
|
||||
};
|
||||
|
||||
const getColorNodeX = () => {
|
||||
if (windowWidth < 800) {
|
||||
return offsetLeft + 440;
|
||||
} 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: 5 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'colorpicker',
|
||||
data: { color: 'green', value: 100, onChange },
|
||||
sourcePosition: 'right',
|
||||
position: { x: offsetLeft, y: 150 },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: { color: 'blue', value: 165, onChange },
|
||||
type: 'colorpicker',
|
||||
sourcePosition: 'right',
|
||||
position: { x: offsetLeft + 120, y: 300 },
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: { label: 'rgb(105, 100, 165)' },
|
||||
targetPosition: 'left',
|
||||
sourcePosition: 'right',
|
||||
position: { x: getColorNodeX(), y: 180 },
|
||||
style: {
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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} />;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
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: '20px' }}
|
||||
>
|
||||
{tokens
|
||||
.filter((line) => line.some((l) => l.content))
|
||||
.map((line, i) => (
|
||||
<div key={i} {...getLineProps({ line, key: i })}>
|
||||
{line.map((token, key) => (
|
||||
<span key={key} {...getTokenProps({ token, key })} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { MDXRenderer } from 'gatsby-plugin-mdx';
|
||||
|
||||
import { H1, H2, H3, H4, Text } from 'components/Typo';
|
||||
import CodeBlock from 'components/Mdx/CodeBlock';
|
||||
import CenterContent from 'components/CenterContent';
|
||||
|
||||
const doNotWrap = ['BlogpostAd'];
|
||||
|
||||
const MdxWrapper = ({ children }) => {
|
||||
return children.map((child, i) =>
|
||||
doNotWrap.includes(child.props?.mdxType) ? (
|
||||
child
|
||||
) : (
|
||||
<CenterContent key={i} mb={2} maxWidth="680px">
|
||||
{child}
|
||||
</CenterContent>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const PostWrapper = styled.div`
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gatsby-resp-image-wrapper {
|
||||
border: 1px solid ${(p) => p.theme.colors.silver};
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const CustomComponents = {
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
h3: H3,
|
||||
h4: H4,
|
||||
p: Text,
|
||||
pre: (props) => <div {...props} />,
|
||||
code: CodeBlock,
|
||||
wrapper: MdxWrapper,
|
||||
};
|
||||
|
||||
const Mdx = ({ content = null }) => {
|
||||
return (
|
||||
<MDXProvider components={CustomComponents}>
|
||||
<PostWrapper>
|
||||
<MDXRenderer>{content}</MDXRenderer>
|
||||
</PostWrapper>
|
||||
</MDXProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Mdx;
|
||||
@@ -0,0 +1,30 @@
|
||||
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 left =
|
||||
typeof window !== 'undefined' && window.innerWidth > 1000
|
||||
? Math.min(350, window.innerWidth * 0.3) / 2
|
||||
: 0;
|
||||
|
||||
const DocWrapper = styled(Box)`
|
||||
max-width: 620px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export default ({ children, menu = [], ...rest }) => (
|
||||
<Page {...rest} footerBorder>
|
||||
<Wrapper>
|
||||
<Sidebar menu={menu} isDocs />
|
||||
<DocWrapper>{children}</DocWrapper>
|
||||
</Wrapper>
|
||||
</Page>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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/webkid-social.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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Box } from 'reflexbox';
|
||||
import { Link } from 'gatsby';
|
||||
import Img from 'gatsby-image';
|
||||
|
||||
import { H2, H4, TextLight } from 'components/Typo';
|
||||
import { getThemeColor, getThemeSpacePx, device } from 'utils/css-utils';
|
||||
|
||||
const Teaser = styled(Box)`
|
||||
position: relative;
|
||||
|
||||
.gatsby-image-wrapper {
|
||||
transition: transform 200ms ease, box-shadow 200ms ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.gatsby-image-wrapper {
|
||||
transform-origin: center center;
|
||||
transform: scale(1.025);
|
||||
box-shadow: 0 2px 14px 2px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledImg = styled(Img)`
|
||||
border-radius: 4px;
|
||||
margin-bottom: ${getThemeSpacePx(3)};
|
||||
`;
|
||||
|
||||
const ReadMore = styled(Box)`
|
||||
color: ${(p) => p.theme.colors.text};
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
`;
|
||||
|
||||
const TextWrapper = styled.div`
|
||||
padding: 0;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
|
||||
&& {
|
||||
a,
|
||||
a:visited,
|
||||
a:hover,
|
||||
a:focus,
|
||||
a:active {
|
||||
color: ${getThemeColor('silverLighten60')};
|
||||
}
|
||||
}
|
||||
|
||||
${TextLight} {
|
||||
color: ${getThemeColor('silverDarken30')};
|
||||
}
|
||||
|
||||
${H2} {
|
||||
color: ${getThemeColor('silverLighten60')};
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
${H4} {
|
||||
font-weight: 400;
|
||||
color: ${getThemeColor('silverDarken30')};
|
||||
}
|
||||
|
||||
@media ${device.tablet} {
|
||||
width: 80%;
|
||||
}
|
||||
`;
|
||||
|
||||
const ProjectTeaser = ({
|
||||
kicker = '',
|
||||
title = '',
|
||||
text = '',
|
||||
fluid = null,
|
||||
slug = '/',
|
||||
}) => {
|
||||
return (
|
||||
<Link to={slug}>
|
||||
<Teaser mb={[4, 5, 6]}>
|
||||
<StyledImg fluid={fluid} />
|
||||
|
||||
<TextWrapper>
|
||||
<TextLight>{kicker}</TextLight>
|
||||
<H2 as="div">{title}</H2>
|
||||
<H4 as="div">{text}</H4>
|
||||
|
||||
<ReadMore mt={3}>Read more</ReadMore>
|
||||
</TextWrapper>
|
||||
</Teaser>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectTeaser;
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Box } from 'reflexbox';
|
||||
import { Link } from 'gatsby';
|
||||
|
||||
import { H3, H2, H4, TextLight, Text } from 'components/Typo';
|
||||
import {
|
||||
px,
|
||||
getThemeColor,
|
||||
getThemeSpacePx,
|
||||
rgba,
|
||||
device,
|
||||
} from 'utils/css-utils';
|
||||
|
||||
const Wrapper = styled(Box)`
|
||||
border-radius: 6px;
|
||||
background: ${(props) =>
|
||||
props.isDark
|
||||
? props.theme.colors.dark.cardBackground
|
||||
: props.theme.colors.light.background};
|
||||
background-image: ${(props) =>
|
||||
props.imgSrc ? `url(${props.imgSrc})` : 'none'};
|
||||
background-repeat: no-repeat;
|
||||
background-position: ${(props) => props.imgPosition};
|
||||
color: ${(p) => p.theme.colors[p.isDark ? 'dark' : 'light'].text};
|
||||
background-size: cover;
|
||||
flex-grow: 1;
|
||||
height: 270px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.scale {
|
||||
transition: transform 200ms ease, box-shadow 200ms ease;
|
||||
transform-style: preserve-3d;
|
||||
backface-visibility: hidden;
|
||||
|
||||
&:hover {
|
||||
transform-origin: center center;
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 2px 12px 2px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
&& {
|
||||
a,
|
||||
a:visited,
|
||||
a:hover,
|
||||
a:focus,
|
||||
a:active {
|
||||
color: ${(p) => p.theme.colors[p.isDark ? 'dark' : 'light'].text};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const getTextShadow = (props) => {
|
||||
const color = props.theme.colors[props.isDark ? 'light' : 'dark'].text;
|
||||
const rgbaColor = rgba(color, props.isDark ? 0.2 : 0.8);
|
||||
return `-1px 0 14px ${rgbaColor}, 0 1px 14px ${rgbaColor}, 1px 0 14px ${rgbaColor}, 0 -1px 14px ${rgbaColor};`;
|
||||
};
|
||||
|
||||
const TextWrapper = styled.div`
|
||||
padding: ${getThemeSpacePx(3)};
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
text-shadow: ${getTextShadow};
|
||||
|
||||
${H2} {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
${H3} {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
${TextLight} {
|
||||
color: ${(p) =>
|
||||
p.isDark
|
||||
? getThemeColor('silverDarken30')
|
||||
: getThemeColor('silverDarken60')};
|
||||
}
|
||||
|
||||
${Text} {
|
||||
color: ${(p) =>
|
||||
p.isDark
|
||||
? getThemeColor('silverDarken30')
|
||||
: getThemeColor('silverDarken60')};
|
||||
}
|
||||
|
||||
${H4} {
|
||||
color: ${getThemeColor('silverDarken30')};
|
||||
}
|
||||
|
||||
@media ${device.tablet} {
|
||||
width: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
const ProjectTeaser = ({
|
||||
kicker = '',
|
||||
title = '',
|
||||
text = '',
|
||||
imgSrc = null,
|
||||
isDark = false,
|
||||
imgPosition = null,
|
||||
imgSize = '100%',
|
||||
slug = '/',
|
||||
hoverEffect = null,
|
||||
}) => {
|
||||
const imagePosition = imgPosition ? imgPosition : 'center right';
|
||||
|
||||
return (
|
||||
<Link to={slug}>
|
||||
<Wrapper
|
||||
imgSrc={imgSrc}
|
||||
isDark={isDark}
|
||||
imgPosition={imagePosition}
|
||||
imgSize={imgSize}
|
||||
className={hoverEffect}
|
||||
>
|
||||
<TextWrapper isDark={isDark}>
|
||||
<TextLight>{kicker}</TextLight>
|
||||
<H3 as="div">{title}</H3>
|
||||
<Text as="div">{text}</Text>
|
||||
</TextWrapper>
|
||||
</Wrapper>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectTeaser;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,53 @@
|
||||
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 } from 'components/Typo';
|
||||
import { getThemeColor } from 'utils/css-utils';
|
||||
|
||||
const gridPadding = 2;
|
||||
|
||||
const RoundImage = styled(Img)`
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
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')};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Showcases = () => {
|
||||
const showcases = useShowcaseImages();
|
||||
|
||||
return (
|
||||
<CenterContent>
|
||||
<Flex marginX={-gridPadding}>
|
||||
{showcases.map((showcase) => (
|
||||
<Box key={showcase.title} width={1 / 3} px={gridPadding}>
|
||||
<RoundImage fluid={showcase.image.childImageSharp.fluid} />
|
||||
<Title>{showcase.title}</Title>
|
||||
<Link href={showcase.url} target="_blank" rel="noopener noreferrer">
|
||||
{showcase.url}
|
||||
</Link>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
</CenterContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default Showcases;
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import Link from 'gatsby-link';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { getThemeColor, getThemeSpacePx } from 'utils/css-utils';
|
||||
|
||||
const Aside = styled.aside`
|
||||
width: 30%;
|
||||
max-width: 300px;
|
||||
padding: 16px;
|
||||
border-right: 1px solid ${getThemeColor('silverLighten30')};
|
||||
`;
|
||||
|
||||
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 }) => {
|
||||
return (
|
||||
<Aside>
|
||||
<SideBarParts items={menu} level={0} />
|
||||
</Aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
@@ -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="You only need a few lines to get started. You get seamless zooming & panning, different edge and node types, single and multi-selection, controls and more."
|
||||
flowProps={flowProps}
|
||||
withControls
|
||||
fitView
|
||||
/>
|
||||
);
|
||||
@@ -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) => rf.fitView({ padding: 0.2 });
|
||||
|
||||
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: 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. Implement complex UIs inside your nodes or add functionality to your edges. React Flow comes with a lot of event handlers."
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,89 @@
|
||||
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';
|
||||
|
||||
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: 5px;
|
||||
|
||||
.react-flow__controls {
|
||||
opacity: ${(p) => (p.isDark ? 0.5 : 1)};
|
||||
}
|
||||
`;
|
||||
|
||||
const DocsLink = styled(Link)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
`;
|
||||
|
||||
const Description = ({ title, description }) => (
|
||||
<Box width={[1, 1, 0.35]}>
|
||||
<H2>{title}</H2>
|
||||
<Text>{description}</Text>
|
||||
<DocsLink to="/docs">
|
||||
Documentation{' '}
|
||||
<Icon width={24} name="arrow_right" colorizeStroke strokeColor="red" />
|
||||
</DocsLink>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default ({
|
||||
title,
|
||||
description,
|
||||
textPosition = 'left',
|
||||
flowProps,
|
||||
withControls = false,
|
||||
withMinimap = false,
|
||||
isDark = false,
|
||||
linesBg = false,
|
||||
children,
|
||||
}) => {
|
||||
const bgColor = isDark ? baseColors.violetLighten60 : baseColors.violet;
|
||||
|
||||
return (
|
||||
<Wrapper mb={[6, 6, 7]}>
|
||||
{textPosition === 'left' && (
|
||||
<Description title={title} description={description} />
|
||||
)}
|
||||
<ReactFlowWrapper width={[1, 1, 0.6]} isDark={isDark}>
|
||||
{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 title={title} description={description} />
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
@@ -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};
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user