website v2 skeleton

Summary:
Sets up a skeleton for the new yoga website using gatsby static site generator

allow-large-files

Reviewed By: emilsjolander

Differential Revision: D6952326

fbshipit-source-id: 7579bc80bec21552689da5b78f3d960910ff13bb
This commit is contained in:
Daniel Büchele
2018-02-12 09:28:31 -08:00
committed by Facebook Github Bot
parent b08bd572ef
commit e43bb9da19
49 changed files with 25219 additions and 0 deletions

8
website/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Project dependencies
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
.cache/
# Build directory
public/
.DS_Store
yarn-error.log

6
website/README.md Normal file
View File

@@ -0,0 +1,6 @@
# Yoga documentation and playground
This uses [gatsby.js](https://www.gatsbyjs.org/).
- Run `yarn develop` during development
- Run `yarn build` to generate site

View File

@@ -0,0 +1,17 @@
---
path: "docs/flexDirection"
title: "Flex Direction"
hasPlayground: true
---
## Flex Direction
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec
sodales libero, sit amet tempus diam. Vivamus finibus vestibulum
est. Vestibulum feugiat pellentesque diam vel hendrerit. Nunc
pretium sollicitudin magna sed pharetra.
Duis bibendum dapibus quam ac rutrum. Suspendisse potenti. Aliquam
est sapien, gravida ac turpis iaculis, convallis rutrum justo. Sed
est augue, pellentesque eleifend mauris non, ultrices aliquam purus.
Duis sed lorem a lectus feugiat fringilla eu non elit.

43
website/gatsby-config.js Normal file
View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
module.exports = {
siteMetadata: {
title: 'Gatsby Default Starter',
},
plugins: [
'gatsby-plugin-react-next',
'gatsby-plugin-react-helmet',
{
resolve: `gatsby-plugin-less`,
options: {
theme: {
'primary-color': '#95DDCF',
},
},
},
{
resolve: 'gatsby-plugin-antd',
options: {
style: true,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/docs`,
name: 'markdown-pages',
},
},
'gatsby-transformer-remark',
],
};

49
website/gatsby-node.js Normal file
View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
const path = require('path');
exports.createPages = ({boundActionCreators, graphql}) => {
const {createPage} = boundActionCreators;
const withPlayground = path.resolve(`src/templates/withPlayground.js`);
const withoutPlayground = path.resolve(`src/templates/withoutPlayground.js`);
return graphql(`
{
allMarkdownRemark {
edges {
node {
frontmatter {
path
hasPlayground
}
html
}
}
}
}
`).then(result => {
if (result.errors) {
return Promise.reject(result.errors);
}
result.data.allMarkdownRemark.edges.forEach(({node}) => {
createPage({
path: node.frontmatter.path,
component: node.frontmatter.hasPlayground
? withPlayground
: withoutPlayground,
context: node,
});
});
});
};

12381
website/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
website/package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "gatsby-starter-default",
"description": "Gatsby default starter",
"version": "1.0.0",
"author": "Kyle Mathews <mathews.kyle@gmail.com>",
"dependencies": {
"antd": "^3.2.0",
"gatsby": "^1.9.158",
"gatsby-link": "^1.6.34",
"gatsby-plugin-antd": "^1.0.10",
"gatsby-plugin-less": "^1.1.4",
"gatsby-plugin-react-helmet": "^2.0.3",
"gatsby-plugin-react-next": "^1.0.8",
"gatsby-source-filesystem": "^1.5.18",
"gatsby-transformer-remark": "^1.7.31",
"immutable": "^4.0.0-rc.9",
"react-helmet": "^5.2.0",
"react-syntax-highlighter": "^7.0.0",
"yoga-layout": "^1.9.0"
},
"keywords": [
"gatsby"
],
"license": "MIT",
"main": "n/a",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"src/**/*.js\"",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"prettier": "^1.10.2"
}
}

View File

@@ -0,0 +1,4 @@
.DocsSidebar {
width: 350px;
padding: 20px;
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import './DocsSidebar.css';
type Props = {
children: any,
};
export default (props: Props) => (
<div className="DocsSidebar">{props.children}</div>
);

View File

@@ -0,0 +1,10 @@
.Footer {
background: #1c2126;
min-height: 100px;
color: rgba(255, 255, 255, 0.6);
padding: 20px 0;
}
.Footer a {
color: white;
}

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import Padded from './Padded';
import './Footer.css';
export default () => (
<footer className="Footer">
<Padded>footer</Padded>
</footer>
);

View File

@@ -0,0 +1,7 @@
.Padded {
width: 100%;
max-width: 1000px;
padding: 0 20px;
margin-left: auto;
margin-right: auto;
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import './Padded.css';
type Props = {
children: any,
className?: string,
};
export default (props: Props) => (
<div className={`Padded ${props.className || ''}`}>{props.children}</div>
);

View File

@@ -0,0 +1,27 @@
@import url('https://fonts.googleapis.com/css?family=Barlow');
html,
body {
margin: 0;
padding: 0;
font-family: 'Barlow', sans-serif;
color: #303845;
font-size: 16px;
}
h1,
h2 {
font-weight: 500;
}
.Page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.PageContent {
display: flex;
flex-direction: column;
flex-grow: 1;
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import Toolbar from './Toolbar';
import Footer from './Footer';
import './Page.css';
type Props = {|
children: any,
title?: string,
className?: string,
|};
export default (props: Props) => (
<div className={`Page ${props.className || ''}`}>
{/* <Head>
<link
rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/antd/3.2.0/antd.min.css"
/>
<link href="//fonts.googleapis.com/css?family=Barlow" rel="stylesheet" />
<title>Yoga Layout{props.title ? ` | ${props.title}` : ''}</title>
</Head> */}
<Toolbar />
<div className="PageContent">{props.children}</div>
<Footer />
</div>
);

View File

@@ -0,0 +1,20 @@
.Code {
display: flex;
flex-direction: column;
max-height: 100%;
}
.CodeContainer {
overflow: scroll;
}
.CodeLanguageSelector.ant-radio-group {
display: flex;
margin: 10px 15px;
}
.CodeLanguageSelector.ant-radio-group .ant-radio-button-wrapper {
flex-grow: 1;
flex-basis: 0;
text-align: center;
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import SyntaxHighlighter, {
registerLanguage,
} from 'react-syntax-highlighter/prism-light';
import styles from 'react-syntax-highlighter/styles/prism/prism';
import jsx from 'react-syntax-highlighter/languages/prism/jsx';
//import javascript from 'react-syntax-highlighter/languages/prism/javascript';
import java from 'react-syntax-highlighter/languages/prism/java';
import objectivec from 'react-syntax-highlighter/languages/prism/objectivec';
import type {LayoutRecordT} from './LayoutRecord';
import {Radio} from 'antd';
import CodeJavaScript from './CodeJavaScript';
import CodeLitho from './CodeLitho';
import CodeReactNative from './CodeReactNative';
import CodeComponentKit from './CodeComponentKit';
import './Code.css';
import type {Yoga$Direction} from 'yoga-layout';
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
registerLanguage('jsx', jsx);
//registerLanguage('javascript', javascript);
registerLanguage('java', java);
registerLanguage('objectivec', objectivec);
type Language = 'JavaScript' | 'Litho' | 'ComponentKit' | 'React Native';
type Props = {
layoutDefinition: LayoutRecordT,
direction: Yoga$Direction,
languages: Array<Language>,
};
type State = {
language: Language,
};
export default class Code extends Component<Props, State> {
static defaultProps = {
languages: [/*'JavaScript', */ 'Litho', 'ComponentKit', 'React Native'],
};
state = {
language: 'Litho',
};
generateCode(lang: Language): string {
if (lang === 'JavaScript') {
return CodeJavaScript(this.props.layoutDefinition, this.props.direction);
} else if (lang === 'Litho') {
return CodeLitho(this.props.layoutDefinition, this.props.direction);
} else if (lang === 'ComponentKit') {
return CodeComponentKit(
this.props.layoutDefinition,
this.props.direction,
);
} else if (lang === 'React Native') {
return CodeReactNative(this.props.layoutDefinition, this.props.direction);
} else {
return '';
}
}
getLanguage(): string {
if (this.state.language === 'Litho') {
return 'java';
} else if (this.state.language === 'React Native') {
return 'javascript';
} else if (this.state.language === 'ComponentKit') {
return 'objectivec';
} else {
return this.state.language;
}
}
render() {
return (
<div className="Code">
<RadioGroup
className="CodeLanguageSelector"
onChange={e => this.setState({language: e.target.value})}
value={this.state.language}>
{this.props.languages.map(lang => (
<RadioButton key={lang} value={lang}>
{lang}
</RadioButton>
))}
</RadioGroup>
<div className="CodeContainer">
<SyntaxHighlighter
language={this.getLanguage()}
style={styles}
customStyle={{fontSize: '13px', backgroundColor: 'white'}}
lineNumberStyle={{userSelect: 'none', opacity: 0.5}}
codeTagProps={{style: {tabSize: 4}}}
showLineNumbers>
{this.generateCode(this.state.language)}
</SyntaxHighlighter>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,184 @@
// @flow
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction /* Yoga$Node */} from 'yoga-layout';
type Yoga$Node = any;
const enumLookup = {
flexDirection: {
[yoga.FLEX_DIRECTION_COLUMN]: 'CKFlexboxDirectionVertical',
[yoga.FLEX_DIRECTION_ROW]: 'CKFlexboxDirectionHorizontal',
[yoga.FLEX_DIRECTION_COLUMN_REVERSE]: 'CKFlexboxDirectionVerticalReverse',
[yoga.FLEX_DIRECTION_ROW_REVERSE]: 'CKFlexboxDirectionHorizontalReverse',
},
alignItems: {
[yoga.ALIGN_FLEX_START]: 'CKFlexboxAlignItemsStart',
[yoga.ALIGN_FLEX_END]: 'CKFlexboxAlignItemsEnd',
[yoga.ALIGN_CENTER]: 'CKFlexboxAlignItemsCenter',
[yoga.ALIGN_BASELINE]: 'CKFlexboxAlignItemsBaseline',
[yoga.ALIGN_STRETCH]: 'CKFlexboxAlignItemsStretch',
},
alignSelf: {
[yoga.ALIGN_AUTO]: 'CKFlexboxAlignSelfAuto',
[yoga.ALIGN_FLEX_START]: 'CKFlexboxAlignSelfStart',
[yoga.ALIGN_FLEX_END]: 'CKFlexboxAlignSelfEnd',
[yoga.ALIGN_CENTER]: 'CKFlexboxAlignSelfCenter',
[yoga.ALIGN_BASELINE]: 'CKFlexboxAlignSelfBaseline',
[yoga.ALIGN_STRETCH]: 'CKFlexboxAlignSelfStretch',
},
alignContent: {
[yoga.ALIGN_FLEX_START]: 'CKFlexboxAlignContentStart',
[yoga.ALIGN_FLEX_END]: 'CKFlexboxAlignContentEnd',
[yoga.ALIGN_CENTER]: 'CKFlexboxAlignContentCenter',
[yoga.ALIGN_SPACE_BETWEEN]: 'CKFlexboxAlignContentSpaceBetween',
[yoga.ALIGN_SPACE_AROUND]: 'CKFlexboxAlignContentSpaceAround',
[yoga.ALIGN_STRETCH]: 'CKFlexboxAlignContentStretch',
},
justifyContent: {
[yoga.JUSTIFY_FLEX_START]: 'CKFlexboxJustifyContentStart',
[yoga.JUSTIFY_CENTER]: 'CKFlexboxJustifyContentCenter',
[yoga.JUSTIFY_FLEX_END]: 'CKFlexboxJustifyContentEnd',
[yoga.JUSTIFY_SPACE_BETWEEN]: 'CKFlexboxJustifyContentSpaceBetween',
[yoga.JUSTIFY_SPACE_AROUND]: 'CKFlexboxJustifyContentSpaceAround',
},
flexWrap: {
[yoga.WRAP_NO_WRAP]: 'CKFlexboxWrapNoWrap',
[yoga.WRAP_WRAP]: 'CKFlexboxWrapWrap',
[yoga.WRAP_WRAP_REVERSE]: 'CKFlexboxWrapWrapReverse',
},
positionType: {
[yoga.POSITION_TYPE_RELATIVE]: 'CKFlexboxPositionTypeRelative',
[yoga.POSITION_TYPE_ABSOLUTE]: 'CKFlexboxPositionTypeAbsolute',
},
};
const untouchedLayout = LayoutRecord({});
const untouchedPosition = PositionRecord({});
function keyLookup(key: string): string {
const keyLookup = {
flexWrap: 'wrap',
flexDirection: 'direction',
};
return keyLookup[key] || key;
}
function getLayoutCode(
node: LayoutRecordT,
indent: string = '',
isRoot?: boolean,
): string {
const lines = [];
const isFlexbox = node.children.size > 0;
lines.push(
indent +
`${isRoot ? '' : `.component = \n${indent}`}[${
isFlexbox ? 'CKFlexboxComponent' : 'CKComponent'
}`,
);
lines.push(indent + ` newWithView:{}`);
lines.push(indent + ` size:{${node.width},${node.height}}`);
const CKFlexboxComponentStyle = [
'direction',
'margin',
'justifyContent',
'alignItems',
'alignContent',
'wrap',
'padding',
'border',
];
const CKFlexboxComponentChild = [
'margin',
'padding',
'flexGrow',
'flexShrink',
'flexBasis',
'alignSelf',
'position',
];
if (isFlexbox) {
// render styles
lines.push(indent + ` style:{`);
indent += '\t';
CKFlexboxComponentStyle.forEach(key => {
let line = renderKey(node, key, indent);
if (line) {
lines.push(line);
}
});
indent = indent.substr(-1);
lines.push(indent + ` }`);
// render children
lines.push(indent + ' children:{');
lines.push(
...node.children
.toJSON()
.map(
child =>
`${indent}\t{\n${getLayoutCode(
child,
indent + '\t\t',
)}\n${indent}\t},`,
),
);
lines.push(indent + `}]${isRoot ? ';' : ''}`);
} else {
lines[lines.length - 1] += ']';
CKFlexboxComponentChild.forEach(key => {
let line = renderKey(node, key, indent);
if (line) {
lines.push(line);
}
});
}
return lines.join('\n');
}
function renderKey(node: Yoga$Node, key: string, indent: string): ?string {
if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
const lines = [];
lines.push(indent + `.${key} = {`);
if (key === 'position') {
lines.push(
indent + `\t.type = ${enumLookup.positionType[node.positionType]},`,
);
}
['top', 'left', 'right', 'bottom'].forEach(pKey => {
if (node[key][pKey]) {
lines.push(indent + `\t.${pKey} = ${node[key][pKey]},`);
}
});
lines.push(indent + `},`);
return lines.join('\n');
} else if (node[key] !== untouchedLayout[key]) {
if (enumLookup[key]) {
return indent + `.${keyLookup(key)} = ${enumLookup[key][node[key]]},`;
} else {
console.error(`Unknown property ${key}`);
}
}
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
return ['CKFlexboxComponent *c =', getLayoutCode(root, '\t', true)].join(
'\n',
);
}

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
export const JSEnumLookup = {
justifyContent: 'JUSTIFY_',
alignItems: 'ALIGN_',
alignContent: 'ALIGN_',
alignSelf: 'ALIGN_',
position: 'POSITION_',
flexDirection: 'DIRECTION_',
flexWrap: 'WRAP_',
positionType: 'POSITION_TYPE_',
direction: 'DIRECTION_',
};
function getEnum(yogaEnum: string, value: string | number): string {
return `yoga.${Object.keys(yoga)
.filter(key => key.toLowerCase().startsWith(yogaEnum.toLowerCase()))
.find(key => yoga[key] === value) || value}`;
}
function setProperty(
name: string,
key: string,
value: string,
enumValue?: string,
): string {
return [
name,
'.set',
key[0].toUpperCase() + key.substr(1),
'(',
enumValue ? `${enumValue}, ` : '',
JSEnumLookup[key] ? getEnum(JSEnumLookup[key], value) : value,
');',
].join('');
}
function getLayoutCode(
node: LayoutRecordT,
name: string,
index: number,
): string {
const lines = [];
const childName = (i: number) => `${name === 'root' ? 'node' : name}_${i}`;
lines.push(
...node.children.map((node, i) => getLayoutCode(node, childName(i), i)),
);
lines.push('', `// create node ${name}`, `const ${name} = Node.create();`);
const untouchedNode = LayoutRecord({width: '', height: ''});
Object.keys(untouchedNode.toJS()).forEach(key => {
if (key !== 'children' && untouchedNode[key] !== node[key]) {
if (node[key] instanceof PositionRecord) {
// iterate through position record
const {top, left, right, bottom} = node[key].toJS();
if (
top !== untouchedNode[key].top &&
top === left &&
top === right &&
top === bottom
) {
// all edges
lines.push(setProperty(name, key, node[key].top, getEnum('edge', 8)));
return;
}
const alreadySet = [];
if (top !== untouchedNode[key].top && top === bottom) {
lines.push(setProperty(name, key, node[key].top, getEnum('edge', 7)));
alreadySet.push('top', 'bottom');
}
if (left !== untouchedNode[key].left && left === right) {
lines.push(
setProperty(name, key, node[key].left, getEnum('edge', 6)),
);
alreadySet.push('left', 'right');
}
['left', 'top', 'right', 'bottom'].forEach((pKey, i) => {
if (
node[key][pKey] !== untouchedNode[key][pKey] &&
alreadySet.indexOf(pKey) === -1
) {
lines.push(
setProperty(name, key, node[key][pKey], getEnum('edge', i)),
);
}
});
} else {
lines.push(setProperty(name, key, node[key]));
}
}
});
if (node.children && node.children.size > 0) {
lines.push(
'',
'// insert children',
...node.children.map(
(_, i) => `${name}.insertChild(${childName(i)}, ${i});`,
),
);
}
return lines.join('\n');
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
const rootNodeName = 'root';
return [
`import yoga, {Node} from 'yoga-layout';`,
getLayoutCode(root, rootNodeName, 0),
'',
`${rootNodeName}.calculateLayout(${root.width}, ${root.height}, ${getEnum(
'direction',
direction,
)});`,
`${rootNodeName}.getComputedLayout();`,
].join('\n');
}

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import {JSEnumLookup} from './CodeJavaScript';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
function getEnum(yogaEnum: string, value: string | number): string {
const enumLookup = {
justifyContent: 'Justify',
alignItems: 'Align',
alignContent: 'Align',
alignSelf: 'Align',
position: 'Position',
flexWrap: 'Wrap',
positionType: 'PositionType',
direction: 'Driection',
};
if (!enumLookup[yogaEnum]) {
return String(value);
} else {
const enumValue = Object.keys(yoga)
.filter(key =>
key.toLowerCase().startsWith(JSEnumLookup[yogaEnum].toLowerCase()),
)
.find(key => yoga[key] === value);
return `Yoga${enumLookup[yogaEnum]}.${
enumValue ? enumValue.replace(/^([A-Z]+)_/, '') : value
}`;
}
}
function getLayoutCode(
node: LayoutRecordT,
indent: string = '',
isReturning?: boolean,
): string {
const lines = [];
const flexDirection = {
[yoga.FLEX_DIRECTION_ROW]: 'Row',
[yoga.FLEX_DIRECTION_ROW_REVERSE]: 'RowReverse',
[yoga.FLEX_DIRECTION_COLUMN]: 'Column',
[yoga.FLEX_DIRECTION_COLUMN_REVERSE]: 'ColumnReverse',
};
lines.push(
indent +
`${isReturning ? 'return ' : ''}${
flexDirection[node.flexDirection]
}.create(c)`,
);
if (node.children.size > 0) {
lines.push(
...node.children
.toJSON()
.map(
child =>
`${indent}\t.child(\n${getLayoutCode(child, indent + '\t\t')})`,
),
);
}
const untouchedLayout = LayoutRecord({width: '', height: ''});
const untouchedPosition = PositionRecord({});
Object.keys(node.toJSON()).forEach(key => {
if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
if (key === 'border') {
lines.push(indent + '\t.border(', indent + '\t\tBorder.create(c)');
}
const {top, left, right, bottom} = node[key].toJS();
if (
top !== untouchedPosition.top &&
top === left &&
top === right &&
top === bottom
) {
// all edges
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.ALL, ${node[key].top})`
: `\t.${key}Dip(YogaEdge.ALL, ${node[key].top})`),
);
return;
}
const alreadySet = [];
if (top !== untouchedPosition.top && top === bottom) {
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.VERTICAL, ${node[key].top})`
: `\t.${key}Dip(YogaEdge.VERTICAL, ${node[key].top})`),
);
alreadySet.push('top', 'bottom');
}
if (left !== untouchedPosition.left && left === right) {
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.HORIZONTAL, ${node[key].left})`
: `\t.${key}Dip(YogaEdge.HORIZONTAL, ${node[key].left})`),
);
alreadySet.push('left', 'right');
}
['left', 'top', 'right', 'bottom'].forEach((pKey, i) => {
if (
node[key][pKey] !== untouchedPosition[pKey] &&
alreadySet.indexOf(pKey) === -1
) {
lines.push(
indent +
(key === 'border'
? `\t\t\t.widthDip(YogaEdge.${pKey.toUpperCase()}, ${
node.border[pKey]
})`
: `\t.${key}Dip(YogaEdge.${pKey.toUpperCase()}, ${
node[key][pKey]
})`),
);
}
});
if (key === 'border') {
lines.push(
indent + '\t\t\t.color(YogaEdge.ALL, 0xfff36b7f)',
indent + '\t\t\t.build())',
);
}
} else if (
key !== 'children' &&
key !== 'flexDirection' &&
node[key] !== untouchedLayout[key]
) {
lines.push(indent + `\t.${key}(${getEnum(key, node[key])})`);
}
});
return lines.join('\n');
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
return [
'@LayoutSpec',
'public class PlaygroundComponentSpec {',
'\t@OnCreateLayout',
'\tstatic Component onCreateLayout(ComponentContext c) {',
getLayoutCode(root, '\t\t', true),
'\t\t\t.build();',
'\t}',
'}',
].join('\n');
}

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import yoga from 'yoga-layout';
import LayoutRecord from './LayoutRecord';
import PositionRecord from './PositionRecord';
import {JSEnumLookup} from './CodeJavaScript';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
function getEnum(yogaEnum: string, value: string | number): string {
const enumValue = Object.keys(yoga)
.filter(
key =>
JSEnumLookup[yogaEnum] &&
key.toLowerCase().startsWith(JSEnumLookup[yogaEnum].toLowerCase()),
)
.find(key => yoga[key] === value);
return enumValue
? "'" +
enumValue
.replace(/^([A-Z]+)_/, '')
.replace('_', '-')
.toLowerCase() +
"'"
: String(value);
}
function getLayoutCode(node: LayoutRecordT, indent: string = ''): string {
const lines = [];
const untouchedLayout = LayoutRecord({width: '', height: ''});
const untouchedPosition = PositionRecord({});
lines.push(indent + '<View style={{');
lines.push(indent + ' flex: 1,');
Object.keys(node.toJSON()).forEach(key => {
if (key === 'border' && !node.border.equals(untouchedPosition)) {
['Top', 'Left', 'Right', 'Bottom'].forEach(pKey => {
if (
untouchedPosition[pKey.toLowerCase()] !==
node.border[pKey.toLowerCase()]
) {
lines.push(
indent +
` border${pKey}Width: ${node.border[pKey.toLowerCase()]},`,
);
}
});
} else if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
const {top, left, right, bottom} = node[key].toJS();
if (
top !== untouchedPosition.top &&
top === left &&
top === right &&
top === bottom
) {
// all edges
lines.push(indent + ` ${key}: ${node[key].top},`);
return;
}
const alreadySet = [];
if (top !== untouchedPosition.top && top === bottom) {
lines.push(indent + ` ${key}Vertical: ${node[key].top},`);
alreadySet.push('top', 'bottom');
}
if (left !== untouchedPosition.left && left === right) {
lines.push(indent + ` ${key}Horizontal: ${node[key].left},`);
alreadySet.push('left', 'right');
}
['left', 'top', 'right', 'bottom'].forEach((pKey, i) => {
if (
node[key][pKey] !== untouchedPosition[pKey] &&
alreadySet.indexOf(pKey) === -1
) {
lines.push(
indent +
` ${key}${pKey[0].toUpperCase()}${pKey.substr(1)}: ${
node[key][pKey]
},`,
);
}
});
} else if (key !== 'children' && node[key] !== untouchedLayout[key]) {
lines.push(indent + ` ${key}: ${getEnum(key, node[key])},`);
}
});
if (node.children.size > 0) {
lines.push(indent + '}}>');
} else {
lines.push(indent + '}} />');
}
if (node.children.size > 0) {
lines.push(
...node.children
.toJSON()
.map(child => getLayoutCode(child, indent + ' ')),
);
}
if (node.children.size > 0) {
lines.push(indent + '</View>');
}
return lines.join('\n');
}
export default function generateCode(
root: LayoutRecordT,
direction: Yoga$Direction,
): string {
return [
`import React, {Component} from 'react';`,
`import {View} from 'react-native';`,
'',
'export default class MyLayout extends Component {',
' render() {',
' return (',
getLayoutCode(root, ' '),
' );',
' }',
'};',
].join('\n');
}

View File

@@ -0,0 +1,49 @@
.Editor {
display: flex;
flex-direction: column;
height: 100%;
}
.Editor .field {
display: flex;
}
.Editor .ant-btn {
width: 100%;
}
.Editor h2 {
font-size: 16px;
margin-bottom: 8px;
margin-top: 30px;
}
.Editor h2:first-child {
margin-top: 0;
}
.Editor .ant-tabs-nav .ant-tabs-tab {
margin-left: 16px;
}
.Editor .EditorTabs {
flex-grow: 1;
}
.Editor .ant-tabs {
display: flex;
flex-direction: column;
}
.Editor .ant-tabs-bar {
margin-bottom: 0;
}
.Editor .ant-tabs-tabpane {
overflow-y: scroll;
padding: 15px;
}
.Editor .EditorButtons {
padding: 15px;
}

View File

@@ -0,0 +1,284 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import {Row, Col, Button, Tabs, Input} from 'antd';
import YogaEnumSelect from './YogaEnumSelect';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
import InfoText from './InfoText';
import YogaPositionEditor from './YogaPositionEditor';
import './Editor.css';
const TabPane = Tabs.TabPane;
type Props = {
node: ?LayoutRecordT,
onChangeLayout: (key: string, value: any) => void,
onChangeSetting: (key: string, value: any) => void,
direction: Yoga$Direction,
selectedNodeIsRoot: boolean,
onRemove?: () => void,
onAdd?: () => void,
};
export default class Editor extends Component<Props> {
componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown);
}
onKeyDown = (e: KeyboardEvent) => {
if (
(e.key === 'Delete' || e.key === 'Backspace') &&
this.props.onRemove &&
!(e.target instanceof HTMLInputElement)
) {
this.props.onRemove();
}
};
render() {
const {node, selectedNodeIsRoot} = this.props;
const disabled = !Boolean(node);
return (
<div className="Editor">
<Tabs defaultActiveKey="1" className="EditorTabs">
<TabPane tab="Flex" key="1">
<h2>
Direction
<InfoText>
The direction property specifies the text direction/writing
direction
</InfoText>
</h2>
<YogaEnumSelect
property="DIRECTION"
value={this.props.direction}
onChange={e => this.props.onChangeSetting('direction', e)}
/>
<h2>
Flex direction
<InfoText>Defining the direction of the main-axis</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="FLEX_DIRECTION"
value={node ? node.flexDirection : ''}
onChange={e => this.props.onChangeLayout('flexDirection', e)}
/>
<Row gutter={15} style={{marginTop: 30}}>
<Col span={12}>
<h2>
Flex grow
<InfoText>
Grow factor defined how much space this element should take
up, relative to it's siblings
</InfoText>
</h2>
<Input
type="text"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexGrow : ''}
onChange={e =>
this.props.onChangeLayout('flexGrow', e.target.value)
}
/>
</Col>
<Col span={12}>
<h2>
Flex shrink
<InfoText>
Shrink factor if elements don't fit into the parent node
anymore.
</InfoText>
</h2>
<Input
type="text"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexShrink : ''}
onChange={e =>
this.props.onChangeLayout('flexShrink', e.target.value)
}
/>
</Col>
</Row>
<h2>
Flex wrap
<InfoText>
Wrapping behaviour when child nodes don't fit into a single line
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="WRAP"
value={node ? node.flexWrap : ''}
onChange={e => this.props.onChangeLayout('flexWrap', e)}
/>
</TabPane>
<TabPane tab="Alignment" key="2">
<h2>
Justify content
<InfoText>Aligns child nodes along the main-axis</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="JUSTIFY"
value={node ? node.justifyContent : ''}
onChange={e => this.props.onChangeLayout('justifyContent', e)}
/>
<h2>
Align items
<InfoText>Aligns child nodes along the cross-axis</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="ALIGN"
value={node ? node.alignItems : ''}
onChange={e => this.props.onChangeLayout('alignItems', e)}
/>
<h2>
Align self
<InfoText>
Specifies alignment on the cross-axis for the node itself
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled || selectedNodeIsRoot}
property="ALIGN"
value={node ? node.alignSelf : ''}
onChange={e => this.props.onChangeLayout('alignSelf', e)}
/>
<h2>
Align content
<InfoText>
Alignment of lines along the cross-axis when wrapping
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="ALIGN"
value={node ? node.alignContent : ''}
onChange={e => this.props.onChangeLayout('alignContent', e)}
/>
</TabPane>
<TabPane tab="Layout" key="3">
<h2>
Width &times; height
<InfoText>Dimensions of the node</InfoText>
</h2>
<Row gutter={15}>
<Col span={12}>
<Input
type="text"
placeholder="width"
disabled={disabled}
value={node ? node.width : ''}
onChange={e =>
this.props.onChangeLayout('width', e.target.value)
}
/>
</Col>
<Col span={12}>
<Input
type="text"
placeholder="height"
disabled={disabled}
value={node ? node.height : ''}
onChange={e =>
this.props.onChangeLayout('height', e.target.value)
}
/>
</Col>
</Row>
<h2>
Aspect ratio
<InfoText>
Aspect radio is an additon by Yoga which is handy e.g. for nodes
displaying videos
</InfoText>
</h2>
<Input
type="text"
placeholder="Aspect ratio"
disabled={disabled}
value={node ? node.aspectRatio : ''}
onChange={e =>
this.props.onChangeLayout('aspectRatio', e.target.value)
}
/>
<h2>Box model</h2>
{['padding', 'border', 'margin'].map(property => (
<YogaPositionEditor
property={property}
key={property}
value={node ? node[property] : undefined}
onChange={value => this.props.onChangeLayout(property, value)}
/>
))}
<h2>
Position
<InfoText>
Relative position offsets the node from it's calculated
position. Absolute position removes the node from the flexbox
flow and positions it at the given position.
</InfoText>
</h2>
<YogaEnumSelect
disabled={disabled}
property="POSITION_TYPE"
value={node ? node.positionType : ''}
onChange={e => this.props.onChangeLayout('positionType', e)}
/>
<YogaPositionEditor
property="position"
value={node ? node.position : undefined}
onChange={value => this.props.onChangeLayout('position', value)}
/>
</TabPane>
</Tabs>
<Row gutter={15} className="EditorButtons">
<Col span={12}>
<Button
icon="plus-circle-o"
disabled={!Boolean(this.props.onAdd)}
onClick={this.props.onAdd}
type="primary">
add child node
</Button>
</Col>
<Col span={12}>
<Button
icon="close-circle-o"
disabled={!Boolean(this.props.onRemove)}
onClick={this.props.onRemove}
type="danger">
remove node
</Button>
</Col>
</Row>
</div>
);
}
}

View File

@@ -0,0 +1,9 @@
.InfoText {
max-width: 230px;
line-height: 130%;
}
.InfoTextIcon {
margin-left: 5px;
opacity: 0.5;
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import {Popover, Icon} from 'antd';
import './InfoText.css';
type Props = {
children: any,
};
export default class InfoText extends Component<Props> {
render() {
return (
<Popover
content={<div className="InfoText">{this.props.children}</div>}
trigger="hover">
<Icon className="InfoTextIcon" type="info-circle-o" />
</Popover>
);
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import {Record, List} from 'immutable';
import type {RecordOf} from 'immutable';
import PositionRecord from './PositionRecord';
import type {PositionRecordT} from './PositionRecord';
import yoga from 'yoga-layout';
import type {
Yoga$Align,
Yoga$JustifyContent,
Yoga$FlexDirection,
Yoga$FlexWrap,
Yoga$Yoga$PositionType,
} from 'yoga-layout';
export type LayoutRecordT = RecordOf<{
width?: ?number,
height?: ?number,
justifyContent?: Yoga$JustifyContent,
padding: PositionRecordT,
border: PositionRecordT,
margin: PositionRecordT,
position: PositionRecordT,
positionType: Yoga$Yoga$PositionType,
alignItems?: Yoga$Align,
alignSelf?: Yoga$Align,
alignContent?: Yoga$Align,
flexDirection?: Yoga$FlexDirection,
flexGrow?: number,
flexShrink?: number,
padding?: number,
flexWrap?: Yoga$FlexWrap,
aspectRatio?: number,
children?: List<LayoutDefinition>,
}>;
const r: LayoutRecordT = Record({
width: 100,
height: 100,
justifyContent: yoga.JUSTIFY_FLEX_START,
alignItems: yoga.ALIGN_STRETCH,
alignSelf: yoga.ALIGN_AUTO,
alignContent: yoga.ALIGN_STRETCH,
flexDirection: yoga.FLEX_DIRECTION_ROW,
padding: PositionRecord(),
margin: PositionRecord(),
border: PositionRecord(),
position: PositionRecord(),
positionType: yoga.POSITION_TYPE_RELATIVE,
flexWrap: yoga.WRAP_NO_WRAP,
flexGrow: 0,
flexShrink: 1,
children: List(),
aspectRatio: 'auto',
});
export default r;

View File

@@ -0,0 +1,9 @@
.PositionGuide {
position: absolute;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
user-select: none;
}

View File

@@ -0,0 +1,148 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import PositionRecord from './PositionRecord';
import type {PositionRecordT} from './PositionRecord';
import './PositionGuide.css';
type Props = {
inset?: boolean,
reverse?: boolean,
position: PositionRecordT,
offset: PositionRecordT,
color: string,
};
export default class PositionGuide extends Component<Props> {
static defaultProps = {
offset: PositionRecord({}),
};
render() {
const {position, offset, inset, color, reverse} = this.props;
let {top, left, right, bottom} = position;
let {top: oTop, left: oLeft, right: oRight, bottom: oBottom} = offset;
if (reverse) {
let temp1 = left;
left = right;
right = temp1;
temp1 = oLeft;
oLeft = oRight;
oRight = temp1;
}
if (!top) {
top = 0;
}
if (!left) {
left = 0;
}
if (!right) {
right = 0;
}
if (!bottom) {
bottom = 0;
}
if (!oTop) {
oTop = 0;
}
if (!oLeft) {
oLeft = 0;
}
if (!oRight) {
oRight = 0;
}
if (!oBottom) {
oBottom = 0;
}
if (!inset) {
if (top < 0) {
bottom -= top;
top = 0;
}
if (bottom < 0) {
top -= bottom;
bottom = 0;
}
if (left < 0) {
right -= left;
left = 0;
}
if (right < 0) {
left -= right;
right = 0;
}
}
return [
top !== 0 ? (
<div
key="top"
className="PositionGuide"
style={{
backgroundColor: color,
height: top,
top: inset ? oTop : -top - oTop,
left: inset ? left + oLeft : -left - oLeft,
right: inset ? right + oRight : -right - oRight,
}}>
{top}
</div>
) : null,
left !== 0 ? (
<div
key="left"
className="PositionGuide"
style={{
backgroundColor: color,
width: left,
top: inset ? oTop : -oTop,
bottom: inset ? oBottom : -oBottom,
left: inset ? oLeft : -left - oLeft,
}}>
{left}
</div>
) : null,
right !== 0 ? (
<div
key="right"
className="PositionGuide"
style={{
backgroundColor: color,
width: right,
top: inset ? oTop : -oTop,
bottom: inset ? oBottom : -oBottom,
right: inset ? oRight : -right - oRight,
}}>
{right}
</div>
) : null,
bottom !== 0 ? (
<div
key="bottom"
className="PositionGuide"
style={{
backgroundColor: color,
height: bottom,
bottom: inset ? oBottom : -bottom - oBottom,
left: inset ? left + oLeft : -left - oLeft,
right: inset ? right + oRight : -right - oRight,
}}>
{bottom}
</div>
) : null,
];
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import {Record} from 'immutable';
import type {RecordOf} from 'immutable';
export type PositionRecordT = RecordOf<{
top: string,
right: string,
bottom: string,
left: string,
}>;
const r: PositionRecordT = Record({
top: '',
right: '',
bottom: '',
left: '',
});
export default r;

View File

@@ -0,0 +1,52 @@
.Sidebar {
position: absolute;
z-index: 3;
top: 0;
right: 0;
bottom: 0;
width: 350px;
background: white;
border-left: 1px solid #dddfe2;
transform: translateX(100%);
transition: 0.2s transform;
display: flex;
flex-direction: column;
pointer-events: none;
}
.Sidebar.floating {
margin: 25px;
max-height: calc(100% - 50px);
border-radius: 6px;
bottom: auto;
box-shadow: 3px 3px 15px rgba(0, 0, 0, 0.15);
opacity: 0;
transform: translateY(15px);
transition: 0.2s all;
transition-timing-function: ease-out;
}
.Sidebar.visible {
transform: translateX(0);
pointer-events: all;
}
.Sidebar.floating.visible {
transform: translateY(0);
opacity: 1;
}
.SidebarClose {
text-align: right;
padding: 15px;
padding-bottom: 0;
}
.SidebarClose i {
cursor: pointer;
opacity: 0.4;
}
.SidebarClose i:hover {
opacity: 0.6;
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import {Icon} from 'antd';
import './Sidebar.css';
type Props = {
onClose?: () => void,
width?: number,
children: any,
floating?: boolean,
};
export default class Sidebar extends Component<Props> {
render() {
return (
<div
className={`Sidebar ${this.props.visible ? 'visible' : ''} ${
this.props.floating ? 'floating' : ''
}`}
style={{width: this.props.width}}>
{this.props.onClose && (
<div className="SidebarClose">
<Icon type="close" onClick={this.props.onClose} />
</div>
)}
{this.props.children}
</div>
);
}
}

View File

@@ -0,0 +1,9 @@
.YogaEnumSelect.ant-radio-group {
display: flex;
}
.YogaEnumSelect.ant-radio-group .ant-radio-button-wrapper {
flex-grow: 1;
flex-basis: 0;
text-align: center;
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import yoga from 'yoga-layout';
import {Radio, Menu, Dropdown, Button, Icon} from 'antd';
import './YogaEnumSelect.css';
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
type Props = {
property: string,
disabled?: boolean,
value: string | number,
onChange: (value: number) => void,
};
export default class YogaEnumSelect extends Component<Props> {
values: Array<{key: string, value: number}>;
constructor({property}: Props) {
super();
// $FlowFixMe
this.values = Object.keys(yoga)
.map(key => ({key, value: yoga[key]}))
.filter(
({key}) => key.startsWith(property) && key !== `${property}_COUNT`,
);
}
handleMenuClick = ({key}: {key: string}) => {
this.props.onChange(yoga[key]);
};
render() {
const {property, ...props} = this.props;
const selected = this.values.find(
({key, value}) => value === this.props.value,
);
const replacer = new RegExp(`^${property}_`);
return this.values.length > 3 ? (
<Dropdown
disabled={props.disabled}
overlay={
<Menu onClick={this.handleMenuClick}>
{this.values.map(({key, value}) => (
<Menu.Item key={key} value={value}>
{key.replace(replacer, '')}
</Menu.Item>
))}
</Menu>
}>
<Button>
{selected ? selected.key.replace(replacer, '') : 'undefiend'}{' '}
<Icon type="down" />
</Button>
</Dropdown>
) : (
<RadioGroup
{...props}
onChange={e => this.props.onChange(e.target.value)}
defaultValue="a"
className="YogaEnumSelect">
{this.values.map(({key, value}) => (
<RadioButton key={key} value={value}>
{key.replace(new RegExp(`^${property}_`), '')}
</RadioButton>
))}
</RadioGroup>
);
}
}

View File

@@ -0,0 +1,51 @@
.YogaNode {
background: white;
position: absolute;
transform: scale(1);
box-shadow: inset 0 0 0px 1px rgba(48, 56, 69, 0.2);
transition: 0.2s all, 0s outline, 0s box-shadow;
}
.YogaNode .YogaNode {
background: rgba(255, 255, 255, 0.7);
}
.YogaNode > .info {
position: absolute;
opacity: 0;
font-family: monospace;
}
.YogaNode:hover > .info {
opacity: 1;
}
.YogaNode:focus {
outline: 0;
}
.YogaNode.focused {
box-shadow: 0 0 0 2px #95ddcf, 0 0 15px rgba(0, 0, 0, 0.2),
inset 0 0 0px 1px rgba(255, 255, 255, 0.2);
z-index: 2;
}
.YogaNode.invisible {
transform: scale(0);
}
.YogaNode .label {
user-select: none;
pointer-events: none;
position: absolute;
left: 0;
bottom: 0;
right: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 300;
letter-spacing: 1px;
}

View File

@@ -0,0 +1,283 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import yoga, {Node} from 'yoga-layout';
import PositionGuide from './PositionGuide';
import PositionRecord from './PositionRecord';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
import './YogaNode.css';
type Yoga$Node = any;
type ComputedLayout = {|
left: number,
top: number,
width: number,
height: number,
children: Array<ComputedLayout>,
node: Yoga$Node,
|};
type Props = {|
layoutDefinition: LayoutRecordT,
className?: string,
computedLayout?: ComputedLayout,
path: Array<number>,
selectedNodePath?: ?Array<number>,
direction?: Yoga$Direction,
label?: string,
showGuides: boolean,
onClick?: (path: Array<number>) => void,
onDoubleClick?: (path: Array<number>) => void,
|};
type State = {
visible?: boolean,
};
export default class YogaNode extends Component<Props, State> {
node: Yoga$Node;
static defaultProps = {
path: [],
label: 'root',
showGuides: true,
};
state = {};
computedLayout: ?ComputedLayout;
rootNode: ?Yoga$Node;
constructor(props: Props) {
super();
if (!props.computedLayout) {
// is root node
this.calculateLayout(props);
this.state = {
visible: !Boolean(props.computedLayout),
};
}
}
componentDidMount() {
setTimeout(() => this.setState({visible: true}), 200);
}
componentWillReceiveProps(nextProps: Props) {
if (
!nextProps.computedLayout &&
(!this.props.layoutDefinition.equals(nextProps.layoutDefinition) ||
this.props.direction !== nextProps.direction)
) {
// is root node and the layout definition or settings changed
this.calculateLayout(nextProps);
}
}
componentWillUnmount() {
if (this.rootNode) {
this.rootNode.freeRecursive();
}
}
calculateLayout(props: Props) {
const root = this.createYogaNodes(props.layoutDefinition);
root.calculateLayout(
props.layoutDefinition.width,
props.layoutDefinition.height,
props.direction,
);
this.computedLayout = this.getComputedLayout(root);
this.rootNode = root;
}
createYogaNodes = (layoutDefinition: LayoutRecordT): Yoga$Node => {
const root = Node.create();
root.setWidth(layoutDefinition.width);
root.setHeight(layoutDefinition.height);
root.setJustifyContent(layoutDefinition.justifyContent);
root.setAlignItems(layoutDefinition.alignItems);
root.setAlignSelf(layoutDefinition.alignSelf);
root.setFlexGrow(layoutDefinition.flexGrow);
root.setFlexShrink(layoutDefinition.flexShrink);
root.setPadding(yoga.EDGE_TOP, layoutDefinition.padding.top);
root.setPadding(yoga.EDGE_RIGHT, layoutDefinition.padding.right);
root.setPadding(yoga.EDGE_BOTTOM, layoutDefinition.padding.bottom);
root.setPadding(yoga.EDGE_LEFT, layoutDefinition.padding.left);
root.setBorder(yoga.EDGE_TOP, layoutDefinition.border.top);
root.setBorder(yoga.EDGE_RIGHT, layoutDefinition.border.right);
root.setBorder(yoga.EDGE_BOTTOM, layoutDefinition.border.bottom);
root.setBorder(yoga.EDGE_LEFT, layoutDefinition.border.left);
root.setMargin(yoga.EDGE_TOP, layoutDefinition.margin.top);
root.setMargin(yoga.EDGE_RIGHT, layoutDefinition.margin.right);
root.setMargin(yoga.EDGE_BOTTOM, layoutDefinition.margin.bottom);
root.setMargin(yoga.EDGE_LEFT, layoutDefinition.margin.left);
root.setPosition(yoga.EDGE_TOP, layoutDefinition.position.top);
root.setPosition(yoga.EDGE_RIGHT, layoutDefinition.position.right);
root.setPosition(yoga.EDGE_BOTTOM, layoutDefinition.position.bottom);
root.setPosition(yoga.EDGE_LEFT, layoutDefinition.position.left);
root.setPositionType(layoutDefinition.positionType);
root.setDisplay(yoga.DISPLAY_FLEX);
root.setAspectRatio(layoutDefinition.aspectRatio);
root.setFlexWrap(layoutDefinition.flexWrap);
root.setFlexDirection(layoutDefinition.flexDirection);
(layoutDefinition.children || [])
.map(this.createYogaNodes)
.forEach((node, i) => {
root.insertChild(node, i);
});
return root;
};
getComputedLayout = (node: Yoga$Node): ComputedLayout => {
return {
...node.getComputedLayout(),
node,
children: Array.apply(null, Array(node.getChildCount())).map((_, i) =>
this.getComputedLayout(node.getChild(i)),
),
};
};
onClick = (e: SyntheticMouseEvent<>) => {
const {onClick} = this.props;
if (onClick) {
e.stopPropagation();
onClick(this.props.path);
}
};
onDoubleClick = (e: SyntheticMouseEvent<>) => {
const {onDoubleClick} = this.props;
if (onDoubleClick) {
e.stopPropagation();
onDoubleClick(this.props.path);
}
};
showPositionGuides({node}: ComputedLayout) {
const padding = PositionRecord({
top: node.getComputedPadding(yoga.EDGE_TOP),
left: node.getComputedPadding(yoga.EDGE_LEFT),
right: node.getComputedPadding(yoga.EDGE_RIGHT),
bottom: node.getComputedPadding(yoga.EDGE_BOTTOM),
});
const border = PositionRecord({
top: node.getComputedBorder(yoga.EDGE_TOP),
left: node.getComputedBorder(yoga.EDGE_LEFT),
right: node.getComputedBorder(yoga.EDGE_RIGHT),
bottom: node.getComputedBorder(yoga.EDGE_BOTTOM),
});
const margin = PositionRecord({
top: node.getComputedMargin(yoga.EDGE_TOP),
left: node.getComputedMargin(yoga.EDGE_LEFT),
right: node.getComputedMargin(yoga.EDGE_RIGHT),
bottom: node.getComputedMargin(yoga.EDGE_BOTTOM),
});
const position = PositionRecord({
top: node.getPosition(yoga.EDGE_TOP).value,
left: node.getPosition(yoga.EDGE_LEFT).value,
right: node.getPosition(yoga.EDGE_RIGHT).value,
bottom: node.getPosition(yoga.EDGE_BOTTOM).value,
});
return [
<PositionGuide
key="border"
inset
position={border}
color="rgba(251, 170, 51, 0.15)"
reverse={node.getFlexWrap() === yoga.WRAP_WRAP_REVERSE}
/>,
<PositionGuide
key="padding"
inset
offset={border}
position={padding}
color="rgba(123, 179, 41, 0.1)"
reverse={node.getFlexWrap() === yoga.WRAP_WRAP_REVERSE}
/>,
<PositionGuide
key="margin"
position={margin}
color="rgba(214, 43, 28, 0.1)"
/>,
<PositionGuide
key="position"
offset={margin}
position={position}
color="rgba(115, 51, 205, 0.1)"
/>,
];
}
render() {
const {
layoutDefinition,
className,
path,
selectedNodePath,
label,
} = this.props;
// $FlowFixMe
const computedLayout: ComputedLayout =
this.props.computedLayout || this.computedLayout;
const {left, top, width, height, children} = computedLayout;
const isFocused = selectedNodePath && selectedNodePath.length === 0;
return (
<div
className={`YogaNode ${isFocused ? 'focused' : ''} ${className || ''} ${
this.state.visible ? '' : 'invisible'
}`}
style={{left, top, width, height}}
onDoubleClick={this.onDoubleClick}
onClick={this.onClick}>
{label && <div className="label">{label}</div>}
{isFocused &&
this.props.showGuides &&
this.showPositionGuides(computedLayout)}
{(children || []).map((child: ComputedLayout, i) => (
<YogaNode
key={i}
computedLayout={child}
label={String(i + 1)}
layoutDefinition={layoutDefinition.children.get(i)}
selectedNodePath={
selectedNodePath &&
selectedNodePath.length > 0 &&
selectedNodePath[0] === i
? selectedNodePath.slice(1)
: null
}
path={path.concat(i)}
onClick={this.props.onClick}
onDoubleClick={this.props.onDoubleClick}
showGuides={this.props.showGuides}
/>
))}
</div>
);
}
}

View File

@@ -0,0 +1,22 @@
.YogaPositionEditor {
text-align: center;
margin-top: 20px;
margin-bottom: 20px;
width: 60%;
margin-left: auto;
margin-right: auto;
}
.YogaPositionEditor input {
width: 55px;
text-align: center;
}
.YogaPositionEditorRow {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
font-size: 16px;
font-weight: bold;
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import {Input} from 'antd';
import PositionRecord from './PositionRecord';
import type {PositionRecordT} from './PositionRecord';
import './YogaPositionEditor.css';
type Props = {
value: PositionRecordT,
property: string,
onChange: (value: PositionRecordT) => void,
};
export default class YogaPositionEditor extends Component<Props> {
static defaultProps = {
value: PositionRecord(),
};
render() {
const {onChange, value, property} = this.props;
return (
<div className="YogaPositionEditor">
<Input
type="text"
value={value.top}
onChange={e => onChange(value.set('top', e.target.value))}
/>
<div className="YogaPositionEditorRow">
<Input
type="text"
value={value.left}
onChange={e => onChange(value.set('left', e.target.value))}
/>
{property}
<Input
type="text"
value={value.right}
onChange={e => onChange(value.set('right', e.target.value))}
/>
</div>
<Input
type="text"
value={value.bottom}
onChange={e => onChange(value.set('bottom', e.target.value))}
/>
</div>
);
}
}

View File

@@ -0,0 +1,34 @@
.PlaygroundContainer {
display: flex;
flex-direction: row;
flex-grow: 1;
width: 100%;
}
.Playground {
flex-grow: 1;
position: relative;
width: 100%;
overflow: hidden;
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(-90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(
transparent 4px,
#f5f5f5 4px,
#f5f5f5 97px,
transparent 97px
),
linear-gradient(-90deg, #e5e5e5 1px, transparent 1px),
linear-gradient(
-90deg,
transparent 4px,
#f5f5f5 4px,
#f5f5f5 97px,
transparent 97px
),
linear-gradient(#e5e5e5 1px, transparent 1px), #f5f5f5;
background-size: 10px 10px, 10px 10px, 100px 100px, 100px 100px, 100px 100px,
100px 100px, 100px 100px, 100px 100px;
}

View File

@@ -0,0 +1,320 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import yoga from 'yoga-layout';
import YogaNode from './YogaNode';
import Editor from './Editor';
import {List, setIn} from 'immutable';
import PositionRecord from './PositionRecord';
import LayoutRecord from './LayoutRecord';
// import Toolbar from './Toolbar';
import Code from './Code';
import Sidebar from './Sidebar';
import type {LayoutRecordT} from './LayoutRecord';
import type {Yoga$Direction} from 'yoga-layout';
import './index.css';
type Props = {
layoutDefinition: LayoutRecordT,
direction: Yoga$Direction,
maxDepth: number,
maxChildren?: number,
minChildren?: number,
selectedNodePath?: Array<number>,
showGuides: boolean,
className?: string,
height?: string | number,
renderSidebar?: (layoutDefinition: LayoutRecordT, onChange: Function) => any,
};
type State = {
selectedNodePath: ?Array<number>,
layoutDefinition: LayoutRecordT,
direction: Yoga$Direction,
showCode: boolean,
};
function getPath(path: Array<number>): Array<mixed> {
return path.reduce((acc, cv) => acc.concat('children', cv), []);
}
export default class Playground extends Component<Props, State> {
_containerRef: ?HTMLElement;
static defaultProps = {
layoutDefinition: LayoutRecord({
width: 800,
height: 400,
justifyContent: yoga.JUSTIFY_SPACE_BETWEEN,
alignItems: yoga.ALIGN_FLEX_START,
children: List([LayoutRecord(), LayoutRecord()]),
padding: PositionRecord({
left: '10',
top: '10',
right: '10',
bottom: '10',
}),
margin: PositionRecord({
left: '20',
top: '70',
}),
}),
direction: yoga.DIRECTION_LTR,
maxDepth: 3,
showCode: false,
showGuides: true,
};
state = {
selectedNodePath: this.props.selectedNodePath,
layoutDefinition: this.props.layoutDefinition,
direction: this.props.direction,
showCode: false,
};
componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
// rehydrate
if (window.location.hash && window.location.hash.length > 1) {
try {
const restoredState = JSON.parse(atob(window.location.hash.substr(1)));
this.setState({layoutDefinition: this.rehydrate(restoredState)});
} catch (e) {
window.location.hash = '';
}
}
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown);
}
rehydrate = (node: Object): LayoutRecord => {
let record = LayoutRecord(node);
record = record.set('padding', PositionRecord(record.padding));
record = record.set('border', PositionRecord(record.border));
record = record.set('margin', PositionRecord(record.margin));
record = record.set('position', PositionRecord(record.position));
record = record.set('children', List(record.children.map(this.rehydrate)));
return record;
};
onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
this.hideSidePanes();
}
};
onMouseDown = (e: MouseEvent) => {
if (e.target === this._containerRef) {
this.hideSidePanes();
}
};
hideSidePanes() {
if (!Boolean(this.props.renderSidebar)) {
// only unselect if we don't have an external sidebar, otherwise the
// sidebar may rely on a certain node to be selected
this.setState({
selectedNodePath: null,
showCode: false,
});
}
}
onChangeLayout = (key: string, value: any) => {
const {selectedNodePath} = this.state;
if (selectedNodePath) {
this.modifyAtPath([...getPath(selectedNodePath), key], value);
}
};
onRemove = () => {
const {selectedNodePath, layoutDefinition} = this.state;
if (selectedNodePath) {
const index = selectedNodePath.pop();
const path = getPath(selectedNodePath).concat('children');
const updatedChildren = layoutDefinition.getIn(path).delete(index);
this.modifyAtPath(path, updatedChildren);
this.setState({selectedNodePath: null});
}
};
onAdd = () => {
const {selectedNodePath, layoutDefinition} = this.state;
if (selectedNodePath) {
const path = getPath(selectedNodePath).concat('children');
const updatedChildren = layoutDefinition.getIn(path).push(LayoutRecord());
this.modifyAtPath(
path,
updatedChildren,
selectedNodePath.concat(updatedChildren.size - 1),
);
}
};
modifyAtPath(
path: Array<any>,
value: any,
selectedNodePath?: ?Array<number> = this.state.selectedNodePath,
) {
console.log(setIn);
// $FlowFixMe
const layoutDefinition = setIn(this.state.layoutDefinition, path, value);
this.setState({
layoutDefinition,
selectedNodePath,
});
window.location.hash = btoa(
JSON.stringify(this.removeUnchangedProperties(layoutDefinition)),
);
}
removeUnchangedProperties = (node: LayoutRecordT): Object => {
const untouchedLayout = LayoutRecord({});
const untouchedPosition = PositionRecord({});
const result = {};
if (!node.equals(untouchedLayout)) {
Object.keys(node.toJS()).forEach(key => {
if (key === 'children' && node.children.size > 0) {
result.children = node.children
.toJSON()
.map(this.removeUnchangedProperties);
} else if (
node[key] instanceof PositionRecord &&
!node[key].equals(untouchedPosition)
) {
result[key] = {};
Object.keys(untouchedPosition.toJS()).forEach(position => {
if (node[key][position] !== untouchedPosition[position]) {
result[key][position] = node[key][position];
}
});
} else if (node[key] !== untouchedLayout[key]) {
result[key] = node[key];
}
});
}
return result;
};
getChildrenCountForSelectedPath = (): number => {
const selectedNode: ?LayoutRecordT = (
this.state.selectedNodePath || []
).reduce(
(node: LayoutRecordT, cv) => node.children.get(cv),
this.state.layoutDefinition,
);
return selectedNode ? selectedNode.children.size : 0;
};
onToggleCode = () => {
this.setState({
selectedNodePath: this.state.showCode
? this.state.selectedNodePath
: null,
showCode: !this.state.showCode,
});
};
render() {
const {layoutDefinition, selectedNodePath} = this.state;
const {height} = this.props;
const selectedNode: ?LayoutRecordT = selectedNodePath
? layoutDefinition.getIn(getPath(selectedNodePath))
: null;
const playground = (
<div
className="Playground"
onMouseDown={this.onMouseDown}
style={{height, maxHeight: height}}
ref={ref => {
this._containerRef = ref;
}}>
{/* <Toolbar
onShowCode={
!this.state.showCode
? () => this.setState({selectedNodePath: null, showCode: true})
: undefined
}
/> */}
<YogaNode
layoutDefinition={layoutDefinition}
selectedNodePath={selectedNodePath}
onClick={selectedNodePath =>
this.setState({selectedNodePath, showCode: false})
}
onDoubleClick={this.onAdd}
direction={this.state.direction}
showGuides={this.props.showGuides}
/>
<Sidebar
visible={
Boolean(selectedNodePath) &&
!this.state.showCode &&
!this.props.renderSidebar
}
floating>
<Editor
node={selectedNode}
selectedNodeIsRoot={
selectedNodePath ? selectedNodePath.length === 0 : false
}
onChangeLayout={this.onChangeLayout}
onChangeSetting={(key, value) => this.setState({[key]: value})}
direction={this.state.direction}
onRemove={
selectedNodePath && selectedNodePath.length > 0
? this.onRemove
: undefined
}
onAdd={
selectedNodePath && selectedNodePath.length < this.props.maxDepth
? this.onAdd
: undefined
}
/>
</Sidebar>
<Sidebar
width={500}
visible={this.state.showCode}
onClose={() => this.setState({showCode: false})}>
{/* <Code
layoutDefinition={layoutDefinition}
direction={this.state.direction}
/> */}
</Sidebar>
</div>
);
if (this.props.renderSidebar) {
return (
<div className={`PlaygroundContainer ${this.props.className || ''}`}>
<div>
{this.props.renderSidebar(
this.state.layoutDefinition,
this.onChangeLayout,
)}
</div>
{playground}
</div>
);
} else {
return playground;
}
}
}

View File

@@ -0,0 +1,43 @@
.Toolbar {
border-bottom: 1px solid #dddfe2;
box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.1);
background: white;
height: 50px;
padding: 0 10px;
padding-bottom: 1px;
z-index: 4;
display: flex;
align-items: center;
}
.Toolbar .logo {
display: flex;
height: 100%;
align-items: center;
text-decoration: none;
margin-left: 5px;
}
.Toolbar a {
margin: 0 15px;
}
.Toolbar h1 {
font-size: 20px;
margin: 0;
padding: 0;
margin-left: 3px;
white-space: nowrap;
}
.Toolbar .ToolbarSpacer {
flex-grow: 1;
}
.Toolbar .ToolbarToggle {
font-size: 16px;
}
.Toolbar .ToolbarToggle i {
margin-right: 5px;
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React, {Component} from 'react';
import Link from 'gatsby-link';
import {Icon} from 'antd';
import './Toolbar.css';
type Props = {
onShowCode?: () => void,
};
export default class Toolbar extends Component<Props> {
render() {
return (
<div className="Toolbar">
<Link to="/" className="logo">
<img
src="https://facebook.github.io/yoga/static/logo.svg"
width="42"
alt="Yoga logo"
/>
<h1>Yoga Layout</h1>
</Link>
<div className="ToolbarSpacer" />
<Link to="/docs">Docs</Link>
<Link to="/playground">Playground</Link>
<a href="https://github.com/facebook/yoga">GitHub</a>
{this.props.onShowCode && (
<a className="ToolbarToggle" onClick={this.props.onShowCode}>
<Icon type={'code-o'} />
Code
</a>
)}
</div>
);
}
}

22
website/src/pages/404.js Normal file
View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
const NotFoundPage = () => (
<div>
<h1>NOT FOUND</h1>
<p>You just hit a route that doesn&#39;t exist... the sadness.</p>
</div>
);
export default NotFoundPage;

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import Link from 'gatsby-link';
import Page from '../../components/Page';
import Padded from '../../components/Padded';
import {Row, Col} from 'antd';
export default () => (
<Page>
<Padded>
<h1>Documentation</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis
urna in lacus rutrum vehicula. Curabitur vitae dictum ante, sollicitudin
sollicitudin tortor. Morbi a vehicula justo. Proin luctus tellus vitae
nulla bibendum gravida. Duis varius cursus erat ut tempus. Fusce ex
arcu, accumsan et ultricies at, fermentum sit amet lectus. Donec a neque
nec leo pharetra fermentum.
</p>
<Row>
<Col md={6} sm={12} xs={24}>
<h2>Getting Started</h2>
<Link to="">Litho</Link>
<br />
<Link to="">ComponentKit</Link>
<br />
<Link to="">React Native</Link>
<br />
<Link to="">Addin Yoga to a project</Link>
</Col>
<Col md={6} sm={12} xs={24}>
<h2>Properties</h2>
<Link to="">Width and height</Link>
<br />
<Link to="">Max/min width and height</Link>
<br />
<Link to="docs/flexDirection">Flex direction</Link>
<br />
<Link to="">Justify content</Link>
<br />
<Link to="">Align items/self</Link>
<br />
<Link to="">Flex basis, grow and shrink</Link>
<br />
<Link to="">Margins, paddings, and borders</Link>
<br />
<Link to="">Flex wrap</Link>
<br />
<Link to="">Align content</Link>
<br />
<Link to="">Absolute layout</Link>
<br />
<Link to="">Aspect ratio</Link>
<br />
<Link to="">Layout direction</Link>
</Col>
<Col md={6} sm={12} xs={24}>
<h2>Examples</h2>
<Link to="">Overlays</Link>
<br />
<Link to="">Floating buttons</Link>
<br />
<Link to="">Flexible text</Link>
</Col>
<Col md={6} sm={12} xs={24}>
<h2>Contributing</h2>
<Link to="">Check out the code</Link>
<br />
<Link to="">Running the test suite</Link>
<br />
<Link to="">Making a change</Link>
<br />
<Link to="">Adding a test</Link>
<br />
<Link to="">Adding documentation</Link>
<br />
<Link to="">Opening a pull request</Link>
</Col>
</Row>
</Padded>
</Page>
);

View File

@@ -0,0 +1,12 @@
.IndexPageSection {
padding-top: 80px;
padding-bottom: 80px;
}
.IndexPageSection h1 {
line-height: 130%;
}
.IndexPageSection .button-spacing {
margin-right: 10px;
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import {Button} from 'antd';
import React from 'react';
import Page from '../components/Page';
import Padded from '../components/Padded';
import Playground from '../components/Playground';
import {Row, Col} from 'antd';
import './index.css';
export default () => (
<Page>
<Padded className="IndexPageSection">
<Row>
<Col span={12}>
<h1>Flexible Layouts with Yoga</h1>
<p>
Build flexible layouts on any platform with a highly optimized
layout engine designed with speed, size, and ease of use in mind.
</p>
<Button type="primary" className="button-spacing">
Get started
</Button>
<Button type="primary" ghost>
Try it out
</Button>
</Col>
<Col span={12} />
</Row>
</Padded>
<Playground selectedNodePath={[]} showGuides={false} height={501} />
<Padded className="IndexPageSection">
<Row>
<Col span={12} />
<Col span={12}>
<h1>Integrated Into The Most Optimized Mobile Frameworks</h1>
<p>
Facebook makes use of Yoga to power most of their UI Frameworks.
This ensured layout can be performed quickly and fluidly off of the
main thread to ensure optimal performance. As an added benefit
engineers familiar with one framework can get started quickly with
another thanks to the shared layout engine.
</p>
</Col>
</Row>
</Padded>
<Padded className="IndexPageSection">
<Row>
<Col span={12}>
<h1>Proven In Productions</h1>
<p>
Yoga powers some of the largest apps in the world, including many of
Facebook's products. This should help convince you that Yoga is
ready to handle the scale of almost any app out there.
</p>
</Col>
<Col span={12} />
</Row>
</Padded>
</Page>
);

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import Page from '../components/Page';
import Playground from '../components/Playground';
export default () => (
<Page title="Playground">
<Playground height="100%" />
</Page>
);

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import Page from '../components/Page';
import Playground from '../components/Playground';
import DocsSidebar from '../components/DocsSidebar';
import YogaEnumSelect from '../components/Playground/YogaEnumSelect';
export default ({pathContext}) => {
return (
<Page>
<Playground
selectedNodePath={[]}
showGuides={false}
renderSidebar={(layout, onChange) => (
<DocsSidebar>
<div dangerouslySetInnerHTML={{__html: pathContext.html}} />
<YogaEnumSelect
property="FLEX_DIRECTION"
value={layout.flexDirection}
onChange={e => onChange('flexDirection', e)}
/>
</DocsSidebar>
)}
/>
</Page>
);
};

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
import React from 'react';
import Page from '../components/Page';
import Padded from '../components/Padded';
export default ({pathContext}) => {
return (
<Page>
<Padded>
<div dangerouslySetInnerHTML={{__html: pathContext.html}} />
</Padded>
</Page>
);
};

9813
website/yarn.lock Normal file

File diff suppressed because it is too large Load Diff