feat(website): init

This commit is contained in:
moklick
2020-10-05 10:14:49 +02:00
parent f54b93b319
commit 277e032770
140 changed files with 30517 additions and 0 deletions
+87
View File
@@ -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)};
`;
+40
View File
@@ -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>
);
};
+41
View File
@@ -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>
);
};
+48
View 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;
+252
View File
@@ -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;
+134
View File
@@ -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>
);
};
+103
View 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
View 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} />;
};
@@ -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>
);
};
+58
View File
@@ -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;
+30
View File
@@ -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>
);
+32
View 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
View 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/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;
+51
View 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;
@@ -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;
+53
View File
@@ -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;
+63
View File
@@ -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;
+88
View 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="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
/>
);
+182
View 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) => 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>
);
};
+173
View 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
/>
);
@@ -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>
);
};
+101
View 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};
}
`;