Port more content to website-next (#1435)

Summary:
This builds upon https://github.com/facebook/yoga/pull/1433 and starts porting over and fixing some of the code in website-next.

1. Create a hero similar to current https://yogalayout.com hero
2. Start moving from `antd` and harcoded colors to [Infima](https://infima.dev/docs/getting-started/introduction/) primitives provided by Docusaurus
3. Replaced some more stock docusaurus assets, links, and text with the ones for Yoga.

There is still a lot to do here (not the least, adding real content), but it's beginning to look like a website, and is already pretty snappy. Eventually I want to get SSR working correctly with Playground, which is still a little broken in the port.

Pull Request resolved: https://github.com/facebook/yoga/pull/1435

Test Plan:
**Gatsby Original**
<img width="1795" alt="image" src="https://github.com/facebook/yoga/assets/835219/7670d53a-00a8-4146-a100-e4a05dd77488">

**New (light mode)**
<img width="800" alt="image" src="https://github.com/facebook/yoga/assets/835219/ebe11d15-5f6f-445f-bcc8-9ec51ecfac62">

**New (dark mode)**
<img width="800" alt="image" src="https://github.com/facebook/yoga/assets/835219/ca44a492-46df-410a-8303-baec3029ec49">

Reviewed By: yungsters

Differential Revision: D50523462

Pulled By: NickGerleman

fbshipit-source-id: 61b4610104f695a4e38a7d4bb6a0c2488bd6f89e
This commit is contained in:
Nick Gerleman
2023-11-03 05:23:56 -07:00
committed by Facebook GitHub Bot
parent a20559063e
commit 92860077f9
31 changed files with 3105 additions and 3332 deletions

View File

@@ -17,10 +17,6 @@ on:
- name: Setup
uses: ./.github/actions/setup-js
- name: Build Yoga
run: yarn build
working-directory: javascript
- name: Build Website
run: yarn build
working-directory: website-next

View File

@@ -22,6 +22,7 @@ const {
const {readFile, writeFile} = require('fs/promises');
const chalk = require('chalk');
const glob = require('glob');
const path = require('path');
const which = require('which');
@@ -132,10 +133,37 @@ function runBenchTask() {
};
}
function findExecutable(name, failureMessage) {
const exec = which.sync(name, {nothrow: true});
if (exec) {
return exec;
}
logger.error(chalk.bold.red(failureMessage));
process.exit(1);
}
function tryFindExecutable(name, failureMessage) {
const exec = which.sync(name, {nothrow: true});
if (exec) {
return exec;
}
logger.warn(chalk.bold.yellow(failureMessage));
return exec;
}
function emcmakeGenerateTask() {
return () => {
const emcmake = which.sync('emcmake');
const ninja = which.sync('ninja', {nothrow: true});
const ninja = tryFindExecutable(
'ninja',
'Warning: Install Ninja (e.g. "brew install ninja") for faster builds',
);
const emcmake = findExecutable(
'emcmake',
'Error: Please install the emscripten SDK: https://emscripten.org/docs/getting_started/',
);
const args = [
'cmake',
'-S',
@@ -152,7 +180,10 @@ function emcmakeGenerateTask() {
function cmakeBuildTask(opts) {
return () => {
const cmake = which.sync('cmake');
const cmake = findExecutable(
'cmake',
'Error: Please install CMake (e.g. "brew install cmake")',
);
const args = [
'--build',
'build',

View File

@@ -4,7 +4,8 @@
"private": true,
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
"lint:fix": "eslint . --fix",
"tsc": "yarn workspaces run tsc"
},
"workspaces": [
"javascript",

View File

@@ -7,11 +7,10 @@
// @ts-check
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
import {themes as prismThemes} from 'prism-react-renderer';
/** @type {import('@docusaurus/types').Config} */
const config = {
export default {
title: 'Yoga',
tagline:
'Build flexible layouts on any platform with a highly optimized open source layout engine designed with speed, size, and ease of use in mind.',
@@ -63,13 +62,13 @@ const config = {
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
sidebarId: 'docsSidebar',
position: 'left',
label: 'Tutorial',
label: 'Documentation',
},
{to: '/blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/facebook/docusaurus',
href: 'https://github.com/facebook/yoga',
label: 'GitHub',
position: 'right',
},
@@ -77,6 +76,13 @@ const config = {
},
footer: {
style: 'dark',
logo: {
alt: 'Meta Open Source',
src: 'img/meta_oss.svg',
href: 'https://opensource.fb.com',
width: 300,
height: 64,
},
links: [
{
title: 'Docs',
@@ -117,10 +123,8 @@ const config = {
copyright: `Copyright © ${new Date().getFullYear()} Meta Platforms, Inc.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
}),
};
module.exports = config;

View File

@@ -4,8 +4,8 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"start": "yarn workspace yoga-layout build && docusaurus start",
"build": "yarn workspace yoga-layout build && docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
@@ -17,34 +17,22 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@docusaurus/core": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@mdx-js/react": "^1.6.22",
"antd": "^3.6.5",
"@docusaurus/core": "3.0.0",
"@docusaurus/preset-classic": "3.0.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^1.2.1",
"immutable": "^4.0.0",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-syntax-highlighter": "^8.0.0",
"prism-react-renderer": "^2.1.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"yoga-layout": "^2.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.1",
"@tsconfig/docusaurus": "^1.0.5"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/tsconfig": "3.0.0",
"@docusaurus/types": "3.0.0"
},
"browserslist": "> 0.5%, last 2 versions, Firefox ESR, not dead",
"engines": {
"node": ">=16.14"
}

View File

@@ -21,7 +21,7 @@
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
docsSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*

View File

@@ -1,77 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
type FeatureItem = {
title: string;
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
description: JSX.Element;
};
const FeatureList: FeatureItem[] = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
function Feature({title, Svg, description}: FeatureItem) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
export default function HomepageFeatures(): JSX.Element {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@@ -5,14 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
.features {
display: flex;
align-items: center;
padding: 2rem 0;
.input {
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

View File

@@ -10,7 +10,8 @@
import React from 'react';
import YogaEnumSelect from './YogaEnumSelect';
import YogaPositionEditor from './YogaPositionEditor';
import {Input} from 'antd';
import styles from './EditValue.module.css';
type Props<T> = {
property: string;
@@ -20,6 +21,7 @@ type Props<T> = {
placeholder?: string;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default (props: Props<any>) => {
if (YogaEnumSelect.availableProperties.indexOf(props.property) > -1) {
// @ts-ignore
@@ -31,7 +33,8 @@ export default (props: Props<any>) => {
return <YogaPositionEditor {...props} />;
} else {
return (
<Input
<input
className={styles.input}
type="text"
{...props}
onChange={e => props.onChange(props.property, e.target.value)}

View File

@@ -1,63 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.Editor {
display: flex;
flex-direction: column;
height: 100%;
border-top: 1px solid #E8E8E8;
}
.Editor .field {
display: flex;
}
.Editor .ant-btn {
width: 100%;
}
.Editor h2 {
margin-bottom: 8px;
margin-top: 20px;
font-size: 12px;
font-weight: 700;
color: #444950;
text-transform: uppercase;
}
.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: auto;
min-height: 320px;
height: 100%;
max-height: 25vh;
padding: 15px;
}
.Editor .EditorButtons {
padding: 15px;
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.editor {
display: flex;
flex-direction: column;
height: 100%;
}
.editor h2 {
margin-bottom: 8px;
margin-top: 20px;
font-size: 12px;
font-weight: 700;
color: var(--ifm-color-content-secondary);
text-transform: uppercase;
}
.tabItem {
overflow-y: auto;
max-height: 400px;
}
.editorButtons {
display: flex;
margin-top: auto;
gap: 5px;
}

View File

@@ -7,18 +7,21 @@
* @format
*/
import React, {Component} from 'react';
import {Row, Col, Button, Tabs} from 'antd';
import EditValue from './EditValue';
import type {LayoutRecordType} from './LayoutRecord';
import type {Direction} from 'yoga-layout';
import InfoText from './InfoText';
import './Editor.css';
const TabPane = Tabs.TabPane;
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import React from 'react';
import EditValue from './EditValue';
import styles from './Editor.module.css';
type Props = {
node: LayoutRecordType;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChangeLayout: (key: string, value: any) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChangeSetting: (key: string, value: any) => void;
direction: Direction;
selectedNodeIsRoot: boolean;
@@ -26,67 +29,35 @@ type Props = {
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 = !node;
export default function Editor(props: Props) {
const {node, selectedNodeIsRoot} = props;
const disabled = node == null;
return (
<div className="Editor">
{/* @ts-ignore */}
<Tabs defaultActiveKey="1" className="EditorTabs">
{/* @ts-ignore */}
<TabPane tab="Flex" key="1">
<h2>
Direction
<InfoText doclink="/docs/layout-direction">
Defines the direction of which text and items are laid out
</InfoText>
</h2>
<div className={styles.editor}>
<Tabs block={true}>
<TabItem
value="flex"
label="Flex"
className={styles.tabItem}
default={true}>
<h2>Direction</h2>
<EditValue
property="direction"
value={this.props.direction}
onChange={this.props.onChangeSetting}
value={props.direction}
onChange={props.onChangeSetting}
/>
<h2>
Flex Direction
<InfoText doclink="/docs/flex-direction">
Defines the direction of the main-axis
</InfoText>
</h2>
<h2>Flex Direction</h2>
<EditValue
disabled={disabled}
property="flexDirection"
value={node ? node.flexDirection : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
<Row gutter={15} style={{marginTop: 30}}>
<Col span={8}>
<h2>
Basis
<InfoText doclink="/docs/flex">
Default size of a node along the main axis
</InfoText>
</h2>
<div className="row margin--none">
<div className="col col--4">
<h2>Basis</h2>
<EditValue
// @ts-ignore
type="text"
@@ -94,34 +65,23 @@ export default class Editor extends Component<Props> {
placeholder="auto"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexBasis : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
<Col span={8}>
<h2>
Grow
<InfoText doclink="/docs/flex">
The factor of remaining space should be given to this node
</InfoText>
</h2>
</div>
<div className="col col--4">
<h2>Grow</h2>
<EditValue
// @ts-ignore
type="text"
property="flexGrow"
property="flexGrow margin--none"
placeholder="0"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexGrow : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
<Col span={8}>
<h2>
Shrink
<InfoText doclink="/docs/flex">
The shrink factor of this element if parent has no space
left
</InfoText>
</h2>
</div>
<div className="col col--4">
<h2>Shrink</h2>
<EditValue
// @ts-ignore
type="text"
@@ -129,88 +89,56 @@ export default class Editor extends Component<Props> {
placeholder="1"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexShrink : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
</Row>
</div>
</div>
<h2>
Flex Wrap
<InfoText doclink="/docs/flex-wrap">
Wrapping behaviour when child nodes don't fit into a single line
</InfoText>
</h2>
<h2>Flex Wrap</h2>
<EditValue
disabled={disabled}
property="flexWrap"
value={node ? node.flexWrap : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</TabPane>
{/* @ts-ignore */}
<TabPane tab="Alignment" key="2">
<h2>
Justify Content
<InfoText doclink="/docs/justify-content">
Aligns child nodes along the main-axis
</InfoText>
</h2>
</TabItem>
<TabItem value="alignment" label="Alignment" className={styles.tabItem}>
<h2>Justify Content</h2>
<EditValue
disabled={disabled}
property="justifyContent"
value={node ? node.justifyContent : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
<h2>
Align Items
<InfoText doclink="/docs/align-items">
Aligns child nodes along the cross-axis
</InfoText>
</h2>
<h2>Align Items</h2>
<EditValue
disabled={disabled}
property="alignItems"
value={node ? node.alignItems : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
<h2>
Align Self
<InfoText doclink="/docs/align-items">
Override align items of parent
</InfoText>
</h2>
<h2>Align Self</h2>
<EditValue
disabled={disabled || selectedNodeIsRoot}
property="alignSelf"
value={node ? node.alignSelf : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
<h2>
Align Content
<InfoText doclink="/docs/align-content">
Alignment of lines along the cross-axis when wrapping
</InfoText>
</h2>
<h2>Align Content</h2>
<EditValue
disabled={disabled}
property="alignContent"
value={node ? node.alignContent : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</TabPane>
{/* @ts-ignore */}
<TabPane tab="Layout" key="3" className="ant-tabs-tabpane">
<h2>
Width &times; Height
<InfoText doclink="/docs/width-height">
Dimensions of the node
</InfoText>
</h2>
<Row gutter={15}>
<Col span={12}>
</TabItem>
<TabItem value="layout" label="Layout" className={styles.tabItem}>
<h2>Width &times; Height</h2>
<div className="row margin--none">
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
@@ -218,10 +146,10 @@ export default class Editor extends Component<Props> {
property="width"
disabled={disabled}
value={node ? node.width : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
<Col span={12}>
</div>
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
@@ -229,18 +157,13 @@ export default class Editor extends Component<Props> {
property="height"
disabled={disabled}
value={node ? node.height : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
</Row>
<h2>
Max-Width &times; Max-Height
<InfoText doclink="/docs/min-max">
Maximum dimensions of the node
</InfoText>
</h2>
<Row gutter={15}>
<Col span={12}>
</div>
</div>
<h2>Max-Width &times; Max-Height</h2>
<div className="row margin--none">
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
@@ -248,10 +171,10 @@ export default class Editor extends Component<Props> {
property="maxWidth"
disabled={disabled}
value={node ? node.maxWidth : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
<Col span={12}>
</div>
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
@@ -259,18 +182,13 @@ export default class Editor extends Component<Props> {
property="maxHeight"
disabled={disabled}
value={node ? node.maxHeight : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
</Row>
<h2>
Min-Width &times; Min-Height
<InfoText doclink="/docs/min-max">
Minimum dimensions of the node
</InfoText>
</h2>
<Row gutter={15}>
<Col span={12}>
</div>
</div>
<h2>Min-Width &times; Min-Height</h2>
<div className="row margin--none">
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
@@ -278,10 +196,10 @@ export default class Editor extends Component<Props> {
property="minWidth"
disabled={disabled}
value={node ? node.minWidth : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
<Col span={12}>
</div>
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
@@ -289,17 +207,12 @@ export default class Editor extends Component<Props> {
property="minHeight"
disabled={disabled}
value={node ? node.minHeight : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</Col>
</Row>
</div>
</div>
<h2>
Aspect Ratio
<InfoText doclink="/docs/aspect-ratio">
Width/Height aspect ratio of node
</InfoText>
</h2>
<h2>Aspect Ratio</h2>
<EditValue
// @ts-ignore
type="text"
@@ -307,7 +220,7 @@ export default class Editor extends Component<Props> {
property="aspectRatio"
disabled={disabled}
value={node ? node.aspectRatio : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
{['padding', 'border', 'margin'].map(property => (
@@ -315,55 +228,41 @@ export default class Editor extends Component<Props> {
property={property}
key={property}
value={node ? node[property] : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
disabled={property === 'margin' && selectedNodeIsRoot}
/>
))}
<h2>
Position Type
<InfoText doclink="/docs/absolute-relative-layout">
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>
<h2>Position Type</h2>
<EditValue
disabled={disabled || selectedNodeIsRoot}
property="positionType"
value={node ? node.positionType : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
<EditValue
disabled={selectedNodeIsRoot}
property="position"
value={node ? node.position : undefined}
onChange={this.props.onChangeLayout}
onChange={props.onChangeLayout}
/>
</TabPane>
</TabItem>
</Tabs>
<Row gutter={15} className="EditorButtons">
<Col span={12}>
<Button
icon="plus-circle-o"
disabled={!this.props.onAdd}
onClick={this.props.onAdd}
type="primary">
add child node
</Button>
</Col>
<Col span={12}>
<Button
icon="close-circle-o"
disabled={!this.props.onRemove}
onClick={this.props.onRemove}
type="danger">
remove node
</Button>
</Col>
</Row>
<div className={styles.editorButtons}>
<button
className="button button--block button--primary button--sm"
disabled={!props.onRemove}
onClick={props.onAdd}>
add child
</button>
<button
className="button button--block button--danger button--sm"
disabled={!props.onRemove}
onClick={props.onAdd}>
remove
</button>
</div>
</div>
);
}
}

View File

@@ -1,22 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.InfoText {
max-width: 230px;
line-height: 130%;
}
.InfoText .docs-link {
margin-top: 0px;
font-size: 12px;
font-weight: 600;
}
.InfoTextIcon {
margin-left: 5px;
opacity: 0.5;
}

View File

@@ -1,38 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import React, {Component} from 'react';
import {Popover, Icon} from 'antd';
import Link from '@docusaurus/Link';
import './InfoText.css';
type Props = {
children: any;
doclink: string;
};
export default class InfoText extends Component<Props> {
render() {
return (
<Popover
content={
<div className="InfoText">
<p>{this.props.children}</p>
<Link className="docs-link" to={this.props.doclink}>
DOCUMENTATION
</Link>
</div>
}
trigger="hover">
{/*@ts-ignore*/}
<Icon className="InfoTextIcon" type="info-circle-o" />
</Popover>
);
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
html[data-theme='light'] {
--yg-color-playound-background: var(--ifm-color-gray-200);
}
html[data-theme='dark'] {
--yg-color-playound-background: var(--ifm-color-background);
}
.container {
display: flex;
flex-direction: row;
background-color: var(--yg-color-playound-background);
min-height: 600px;
padding: 10px;
}
.playground {
flex: 1;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}

View File

@@ -16,22 +16,25 @@ import PositionRecord from './PositionRecord';
import LayoutRecord from './LayoutRecord';
import Sidebar from './Sidebar';
import type {LayoutRecordType} from './LayoutRecord';
import './index.css';
import styles from './Playground.module.css';
import clsx from 'clsx';
type Props = {
layoutDefinition: LayoutRecordType;
direction: Direction;
maxDepth: number;
layoutDefinition?: LayoutRecordType;
direction?: Direction;
maxDepth?: number;
maxChildren?: number;
minChildren?: number;
selectedNodePath?: Array<number>;
showGuides: boolean;
showGuides?: boolean;
className?: string;
height?: string | number;
persist?: boolean;
renderSidebar?: (
layoutDefinition: LayoutRecordType,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange: () => any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => any;
};
@@ -126,6 +129,7 @@ export default class Playground extends Component<Props, State> {
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChangeLayout = (key: string, value: any) => {
const {selectedNodePath} = this.state;
if (selectedNodePath) {
@@ -158,7 +162,9 @@ export default class Playground extends Component<Props, State> {
};
modifyAtPath(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
path: Array<any>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any,
selectedNodePath: Array<number> = this.state.selectedNodePath,
) {
@@ -235,11 +241,8 @@ export default class Playground extends Component<Props, State> {
: null;
const playground = (
<div className="playground-background">
<div
className={`Playground ${
this.props.renderSidebar ? '' : 'standalone'
}`}
className={styles.playground}
onMouseDown={this.onMouseDown}
style={{height, maxHeight: height}}
ref={ref => {
@@ -253,17 +256,24 @@ export default class Playground extends Component<Props, State> {
direction={direction}
showGuides={this.props.showGuides}
/>
{!this.props.renderSidebar && (
<Sidebar>
{this.state.selectedNodePath ? (
</div>
);
const sidebarContent = this.props.renderSidebar
? this.props.renderSidebar(
// @ts-ignore
layoutDefinition.getIn(getPath(selectedNodePath)),
this.onChangeLayout,
)
: this.state.selectedNodePath != null && (
<Editor
node={selectedNode}
selectedNodeIsRoot={
selectedNodePath ? selectedNodePath.length === 0 : false
}
onChangeLayout={this.onChangeLayout}
// @ts-ignore
onChangeSetting={(key, value) =>
// @ts-ignore
this.setState({[key]: value})
}
direction={direction}
@@ -273,38 +283,18 @@ export default class Playground extends Component<Props, State> {
: undefined
}
onAdd={
selectedNodePath &&
selectedNodePath.length < this.props.maxDepth
selectedNodePath && selectedNodePath.length < this.props.maxDepth
? this.onAdd
: undefined
}
/>
) : (
<div className="NoContent">
Select a node to edit its properties
</div>
)}
</Sidebar>
)}
</div>
</div>
);
if (this.props.renderSidebar) {
return (
<div className={`PlaygroundContainer ${this.props.className || ''}`}>
<div>
{this.props.renderSidebar(
// @ts-ignore
layoutDefinition.getIn(getPath(selectedNodePath)),
this.onChangeLayout,
)}
</div>
<div className={clsx(styles.container, this.props.className)}>
{playground}
<Sidebar>{sidebarContent}</Sidebar>
</div>
);
} else {
return playground;
}
}
}

View File

@@ -5,18 +5,17 @@
* LICENSE file in the root directory of this source tree.
*/
.Sidebar {
position: absolute;
.placeholder {
color: var(--ifm-color-content-secondary);
margin: auto;
text-align: center;
}
.sidebar {
z-index: 3;
top: 0;
right: 0;
width: 350px;
background: white;
width: 320px;
background: var(--ifm-background-surface-color);
display: flex;
flex-direction: column;
margin: 25px;
max-height: calc(100% - 50px);
border-radius: 6px;
bottom: auto;
box-shadow: 3px 3px 15px rgba(0, 0, 0, 0.15);
padding: 20px;
}

View File

@@ -8,18 +8,30 @@
*/
import React, {Component} from 'react';
import './Sidebar.css';
import styles from './Sidebar.module.css';
import clsx from 'clsx';
type Props = {
width?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
children: any;
};
function PlaceholderContent() {
return (
<div className={styles.placeholder}>
<p>Select a node to edit its properties</p>
</div>
);
}
export default class Sidebar extends Component<Props> {
render() {
return (
<div className="Sidebar" style={{width: this.props.width}}>
{this.props.children}
<div
className={clsx('card', styles.sidebar)}
style={{width: this.props.width}}>
{this.props.children || <PlaceholderContent />}
</div>
);
}

View File

@@ -1,25 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.YogaEnumSelect {
display: flex;
}
.YogaEnumSelect.ant-radio-group .ant-radio-button-wrapper {
flex-grow: 1;
flex-basis: 0;
text-align: center;
}
.YogaEnumSelect .ant-btn {
flex-grow: 1;
flex-basis: 0;
}
.YogaEnumSelect .ant-radio-button-wrapper {
white-space: nowrap;
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.buttonGroup {
display: flex;
}
.button {
flex: 1;
}
.select {
appearance: none;
width: 100%;
padding: calc(var(--ifm-button-padding-vertical) * 0.8) calc(var(--ifm-button-padding-horizontal) * 0.8);
background: transparent;
color: var(--ifm-font-color-base);
border: var(--ifm-button-border-width) solid var(--ifm-color-secondary);
border-radius: var(--ifm-button-border-radius);
font-size: calc(0.875rem * 0.8);
font-weight: var(--ifm-button-font-weight);
line-height: 1.5;
}

View File

@@ -8,11 +8,10 @@
*/
import React, {Component} from 'react';
import clsx from 'clsx';
import Yoga from 'yoga-layout';
import {Radio, Menu, Dropdown, Button, Icon} from 'antd';
import './YogaEnumSelect.css';
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
import styles from './YogaEnumSelect.module.css';
const PROPERTY_LOOKUP = {
flexDirection: 'FLEX_DIRECTION',
@@ -65,41 +64,30 @@ export default class YogaEnumSelect extends Component<Props> {
const selected = this.values.find(({value}) => value === this.props.value);
return this.values.length > 3 ? (
<div className="YogaEnumSelect">
{/*@ts-ignore*/}
<Dropdown
trigger={['click']}
disabled={this.props.disabled}
overlay={
// @ts-ignore
<Menu onClick={this.handleMenuClick}>
<select className={styles.select} name={this.props.property}>
{this.values.map(({key, value}) => (
// @ts-ignore
<Menu.Item key={key} value={value}>
{this.getTitle(property, key)}
</Menu.Item>
<option key={key} value={value}>
{selected ? this.getTitle(property, key) : ''}
</option>
))}
</Menu>
}>
<Button>
{selected ? this.getTitle(property, selected.key) : ''}
{/*@ts-ignore*/}
<Icon type="down" />
</Button>
</Dropdown>
</div>
</select>
) : (
<RadioGroup
{...this.props}
onChange={e => this.props.onChange(this.props.property, e.target.value)}
defaultValue="a"
className="YogaEnumSelect">
<div className={clsx('button-group', styles.buttonGroup)}>
{this.values.map(({key, value}) => (
<RadioButton key={key} value={value}>
<button
className={clsx(
'button',
'button--sm',
'button--outline',
'button--secondary',
value === this.props.value && 'button--active',
styles.button,
)}
onClick={() => this.props.onChange(this.props.property, value)}>
{this.getTitle(property, key)}
</RadioButton>
</button>
))}
</RadioGroup>
</div>
);
}
}

View File

@@ -6,16 +6,29 @@
*/
.YogaNode {
background: white;
box-sizing: border-box;
background: var(--ifm-background-surface-color);
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;
box-shadow: var(--ifm-global-shadow-lw);
cursor: pointer;
animation: yoga-node-fadein 200ms ease;
}
.YogaNode.hover {
background-color: #F0FFF9;
@keyframes yoga-node-fadein {
0% {
transform: scale(1.05);
opacity: 0%;
}
100% {
transform: scale(1.0);
opacity: 100%;
}
}
.YogaNode.hover:not(.focused) {
background-color: var(--ifm-color-emphasis-100);
}
.YogaNode .YogaNode {
@@ -26,13 +39,8 @@
background: rgba(240, 255, 249, 0.7);
}
.YogaNode:focus {
outline: 0;
}
.YogaNode.focused {
box-shadow: 0 0 0 2px #68CFBB, 0 0 15px rgba(0, 0, 0, 0.2),
inset 0 0 0px 1px rgba(255, 255, 255, 0.2);
outline: 2px solid var(--ifm-color-primary);
z-index: 2;
}

View File

@@ -14,6 +14,7 @@ import PositionRecord from './PositionRecord';
import LayoutRecord from './LayoutRecord';
import type {LayoutRecordType} from './LayoutRecord';
import {Direction, Display, Edge, Node, Wrap} from 'yoga-layout';
import clsx from 'clsx';
import './YogaNode.css';
@@ -261,9 +262,14 @@ export default class YogaNode extends Component<Props, State> {
return (
<div
className={`YogaNode ${isFocused ? 'focused' : ''} ${className || ''} ${
this.state.visible ? '' : 'invisible'
} ${this.state.hovered ? 'hover' : ''}`}
className={clsx(
'card',
'YogaNode',
className,
isFocused && 'focused',
this.state.hovered && 'hover',
this.state.visible === false && 'invisible',
)}
style={path.length == 0 ? {width, height} : {left, top, width, height}}
onDoubleClick={this.onDoubleClick}
onMouseMove={this.onMouseMove}

View File

@@ -8,7 +8,6 @@
*/
import React, {Component} from 'react';
import {Input} from 'antd';
import PositionRecord from './PositionRecord';
import type {PositionRecordType} from './PositionRecord';
import './YogaPositionEditor.css';
@@ -33,14 +32,14 @@ export default class YogaPositionEditor extends Component<Props> {
const {onChange, value, property, disabled} = this.props;
return (
<div className="YogaPositionEditor">
<Input
<input
type="text"
value={Number.isNaN(value.top) ? '' : value.top}
disabled={disabled}
onChange={e => onChange(property, value.set('top', e.target.value))}
/>
<div className="YogaPositionEditorRow">
<Input
<input
type="text"
value={Number.isNaN(value.left) ? '' : value.left}
disabled={disabled}
@@ -49,7 +48,7 @@ export default class YogaPositionEditor extends Component<Props> {
}
/>
{property.toUpperCase()}
<Input
<input
type="text"
value={Number.isNaN(value.right) ? '' : value.right}
disabled={disabled}
@@ -58,7 +57,7 @@ export default class YogaPositionEditor extends Component<Props> {
}
/>
</div>
<Input
<input
type="text"
value={Number.isNaN(value.bottom) ? '' : value.bottom}
disabled={disabled}

View File

@@ -1,87 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.PlaygroundContainer {
display: flex;
flex-direction: row;
flex-grow: 1;
width: 100%;
}
.playground-background {
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;
}
.Playground {
display: flex;
flex-grow: 1;
position: relative;
width: 100%;
overflow: hidden;
animation: playground-content-fade-in-frames 50ms ease-in;
}
.Playground > .YogaNode {
margin: auto;
position: static;
align-self: center;
}
.Playground.standalone > .YogaNode {
transform: translateX(-175px);
}
.Playground .Actions {
padding: 15px;
}
.Playground .Actions .ant-btn {
width: 100%;
}
.Playground .NoContent {
border-top: 1px solid #E8E8E8;
font-size: 18px;
padding: 30px 50px;
text-align: center;
color: #D9D9D9;
font-weight: 300;
line-height: 130%;
}
.ant-modal-content {
overflow: hidden;
}
@keyframes playground-content-fade-in-frames {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@@ -13,25 +13,25 @@
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-color-primary-lightest: rgb(99, 183, 168);
--ifm-color-primary-lighter: rgb(70, 159, 143);
--ifm-color-primary-light: rgb(48, 135, 119);
--ifm-color-primary: rgb(33, 111, 97);
--ifm-color-primary-dark: rgb(22, 87, 75);
--ifm-color-primary-darker: rgb(14, 63, 54);
--ifm-color-primary-darkest: rgb(8, 39, 33);
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--ifm-color-primary-lightest: rgb(192, 231, 224);
--ifm-color-primary-lighter: rgb(146, 207, 196);
--ifm-color-primary-light: rgb(106, 183, 169);
--ifm-color-primary: rgb(74, 159, 144);
--ifm-color-primary-dark: rgb(51, 135, 120);
--ifm-color-primary-darker: rgb(34, 111, 97);
--ifm-color-primary-darkest: rgb(22, 87, 75);
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

View File

@@ -10,47 +10,151 @@
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
html[data-theme='light'] {
--yg-color-playound-background: var(--ifm-color-gray-200);
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
html[data-theme='dark'] {
--yg-color-playound-background: var(--ifm-color-background);
}
.buttons {
.heroBanner {
flex: 1;
display: flex;
flex-direction: row;
justify-content: center;
}
.heroRow {
align-items: center;
}
.blueprintColumn {
display: flex;
align-items: center;
justify-content: center;
}
.playgroundFallback {
height: 500px;
width: 100%;
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;
@media (max-width: 996px) {
.blueprintColumn {
display: none;
}
}
.blueprint {
--blueprint-gap: 5%;
--fadein-duration: 500ms;
box-shadow: var(--ifm-global-shadow-tl);
background-color: var(--ifm-background-surface-color);
border-radius: 0.5rem;
}
.blueprintContainer {
position: relative;
width: var(--ifm-col-width);
aspect-ratio: 1.0;
background-color: var(--ifm-color-primary-lighter);
box-shadow: var(--ifm-global-shadow-lw);
}
.blueprintAvatar {
position: absolute;
left: 0;
top: 0;
margin: var(--blueprint-gap) 0 0 var(--blueprint-gap);
width: calc(25% - (var(--blueprint-gap)));
height: calc(25% - (var(--blueprint-gap)));
animation: avatar-fadein var(--fadein-duration) ease;
}
@keyframes avatar-fadein {
0% {
transform: scale(1.1);
opacity: 0%;
}
50% {
transform: scale(1.0);
opacity: 100%;
}
}
.blueprintTitle {
position: absolute;
left: 25%;
top: 0;
right: 10%;
margin: var(--blueprint-gap) var(--blueprint-gap) 0 var(--blueprint-gap);
height: calc(10% - (var(--blueprint-gap)));
animation: title-fadein var(--fadein-duration) ease;
}
.blueprintSubtitle {
position: absolute;
left: 25%;
top: 10%;
right: 30%;
margin: var(--blueprint-gap);
height: calc(10% - (var(--blueprint-gap)));
animation: title-fadein var(--fadein-duration) ease;
}
@keyframes title-fadein {
0% {
transform: scale(1.1);
opacity: 0%;
}
25% {
transform: scale(1.1);
opacity: 0%;
}
75% {
transform: scale(1.0);
opacity: 100%;
}
}
.blueprintContent {
box-sizing: border-box;
position: absolute;
bottom: 0;
margin: var(--blueprint-gap);
width: calc(100% - (var(--blueprint-gap) * 2));
height: calc(75% - (var(--blueprint-gap) * 2));
animation: content-fadein var(--fadein-duration) ease;
}
@keyframes content-fadein {
0% {
transform: scale(1.1);
opacity: 0%;
}
50% {
transform: scale(1.1);
opacity: 0%;
}
100% {
transform: scale(1.0);
opacity: 100%;
}
}
.playgroundSection {
display: flex;
flex-direction: column;
justify-content: center;
height: 600px;
width: 100%;
background-color: var(--yg-color-playound-background);
}
@media (max-width: 996px) {
.playgroundSection {
display: none;
}
}

View File

@@ -8,59 +8,82 @@
import React, {Suspense} from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import BrowserOnly from '@docusaurus/BrowserOnly';
import styles from './index.module.css';
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
function HeroSection() {
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container">
<h1 className="hero__title">{siteConfig.title}</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/intro">
Docusaurus Tutorial - 5min
<header className={clsx('hero', styles.heroBanner)}>
<div className={clsx('row', 'container', styles.heroRow)}>
<div className="col col--6">
<h1 className="hero__title">Yoga Layout</h1>
<p className="hero__subtitle">
A portable and perfomant layout engine targeting web standards
</p>
<Link className="button button--primary button--lg" to="/docs/intro">
Learn more
</Link>
</div>
<div className={clsx(['col col--6', styles.blueprintColumn])}>
<div className={clsx([styles.blueprint, styles.blueprintContainer])}>
<div className={styles.blueprintHeader}>
<div
className={clsx([styles.blueprint, styles.blueprintAvatar])}
/>
<div
className={clsx([styles.blueprint, styles.blueprintTitle])}
/>
<div
className={clsx([styles.blueprint, styles.blueprintSubtitle])}
/>
</div>
<div
className={clsx([styles.blueprint, styles.blueprintContent])}
/>
</div>
</div>
</div>
</header>
);
}
const LazyPlayground = React.lazy(() => import('../components/Playground'));
function ClientPlayground() {
const fallback = <div className={styles.playgroundFallback} />;
const LazyPlayground = React.lazy(
() => import('../components/Playground/Playground'),
);
// Docusaurus SSR does not correctly support top-level await
// 1. https://github.com/facebook/docusaurus/issues/7238
// 2. https://github.com/facebook/docusaurus/issues/9468
function BrowserOnlyPlayground() {
return (
<BrowserOnly fallback={fallback}>
<BrowserOnly fallback={null}>
{() => (
<Suspense fallback={fallback}>
<LazyPlayground />
<Suspense fallback={null}>
<LazyPlayground className={styles.playground} />
</Suspense>
)}
</BrowserOnly>
);
}
export default function Home(): JSX.Element {
const {siteConfig} = useDocusaurusContext();
function PlaygroundSection() {
return (
<Layout
title={`Hello from ${siteConfig.title}`}
description="Description will go into a meta tag in <head />">
<HomepageHeader />
<main>
<HomepageFeatures />
<ClientPlayground />
<main className={styles.playgroundSection}>
<div className="container">
<BrowserOnlyPlayground />
</div>
</main>
);
}
export default function Home(): JSX.Element {
return (
<Layout title="Yoga Layout | A cross-platform layout engine">
<HeroSection />
<PlaygroundSection />
</Layout>
);
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,6 +1,6 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json",
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": ".",
"target": "esnext",

4763
yarn.lock

File diff suppressed because it is too large Load Diff