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 - name: Setup
uses: ./.github/actions/setup-js uses: ./.github/actions/setup-js
- name: Build Yoga
run: yarn build
working-directory: javascript
- name: Build Website - name: Build Website
run: yarn build run: yarn build
working-directory: website-next working-directory: website-next

View File

@@ -22,6 +22,7 @@ const {
const {readFile, writeFile} = require('fs/promises'); const {readFile, writeFile} = require('fs/promises');
const chalk = require('chalk');
const glob = require('glob'); const glob = require('glob');
const path = require('path'); const path = require('path');
const which = require('which'); 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() { function emcmakeGenerateTask() {
return () => { return () => {
const emcmake = which.sync('emcmake'); const ninja = tryFindExecutable(
const ninja = which.sync('ninja', {nothrow: true}); '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 = [ const args = [
'cmake', 'cmake',
'-S', '-S',
@@ -152,7 +180,10 @@ function emcmakeGenerateTask() {
function cmakeBuildTask(opts) { function cmakeBuildTask(opts) {
return () => { return () => {
const cmake = which.sync('cmake'); const cmake = findExecutable(
'cmake',
'Error: Please install CMake (e.g. "brew install cmake")',
);
const args = [ const args = [
'--build', '--build',
'build', 'build',

View File

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

View File

@@ -7,11 +7,10 @@
// @ts-check // @ts-check
const lightCodeTheme = require('prism-react-renderer/themes/github'); import {themes as prismThemes} from 'prism-react-renderer';
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */ /** @type {import('@docusaurus/types').Config} */
const config = { export default {
title: 'Yoga', title: 'Yoga',
tagline: 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.', '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: [ items: [
{ {
type: 'docSidebar', type: 'docSidebar',
sidebarId: 'tutorialSidebar', sidebarId: 'docsSidebar',
position: 'left', position: 'left',
label: 'Tutorial', label: 'Documentation',
}, },
{to: '/blog', label: 'Blog', position: 'left'}, {to: '/blog', label: 'Blog', position: 'left'},
{ {
href: 'https://github.com/facebook/docusaurus', href: 'https://github.com/facebook/yoga',
label: 'GitHub', label: 'GitHub',
position: 'right', position: 'right',
}, },
@@ -77,6 +76,13 @@ const config = {
}, },
footer: { footer: {
style: 'dark', style: 'dark',
logo: {
alt: 'Meta Open Source',
src: 'img/meta_oss.svg',
href: 'https://opensource.fb.com',
width: 300,
height: 64,
},
links: [ links: [
{ {
title: 'Docs', title: 'Docs',
@@ -117,10 +123,8 @@ const config = {
copyright: `Copyright © ${new Date().getFullYear()} Meta Platforms, Inc.`, copyright: `Copyright © ${new Date().getFullYear()} Meta Platforms, Inc.`,
}, },
prism: { prism: {
theme: lightCodeTheme, theme: prismThemes.github,
darkTheme: darkCodeTheme, darkTheme: prismThemes.dracula,
}, },
}), }),
}; };
module.exports = config;

View File

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

View File

@@ -21,7 +21,7 @@
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = { const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure // 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 // 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. * LICENSE file in the root directory of this source tree.
*/ */
.features { .input {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%; width: 100%;
} }
.featureSvg {
height: 200px;
width: 200px;
}

View File

@@ -10,7 +10,8 @@
import React from 'react'; import React from 'react';
import YogaEnumSelect from './YogaEnumSelect'; import YogaEnumSelect from './YogaEnumSelect';
import YogaPositionEditor from './YogaPositionEditor'; import YogaPositionEditor from './YogaPositionEditor';
import {Input} from 'antd';
import styles from './EditValue.module.css';
type Props<T> = { type Props<T> = {
property: string; property: string;
@@ -20,6 +21,7 @@ type Props<T> = {
placeholder?: string; placeholder?: string;
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default (props: Props<any>) => { export default (props: Props<any>) => {
if (YogaEnumSelect.availableProperties.indexOf(props.property) > -1) { if (YogaEnumSelect.availableProperties.indexOf(props.property) > -1) {
// @ts-ignore // @ts-ignore
@@ -31,7 +33,8 @@ export default (props: Props<any>) => {
return <YogaPositionEditor {...props} />; return <YogaPositionEditor {...props} />;
} else { } else {
return ( return (
<Input <input
className={styles.input}
type="text" type="text"
{...props} {...props}
onChange={e => props.onChange(props.property, e.target.value)} 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 * @format
*/ */
import React, {Component} from 'react';
import {Row, Col, Button, Tabs} from 'antd';
import EditValue from './EditValue';
import type {LayoutRecordType} from './LayoutRecord'; import type {LayoutRecordType} from './LayoutRecord';
import type {Direction} from 'yoga-layout'; import type {Direction} from 'yoga-layout';
import InfoText from './InfoText';
import './Editor.css'; import Tabs from '@theme/Tabs';
const TabPane = Tabs.TabPane; import TabItem from '@theme/TabItem';
import React from 'react';
import EditValue from './EditValue';
import styles from './Editor.module.css';
type Props = { type Props = {
node: LayoutRecordType; node: LayoutRecordType;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChangeLayout: (key: string, value: any) => void; onChangeLayout: (key: string, value: any) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChangeSetting: (key: string, value: any) => void; onChangeSetting: (key: string, value: any) => void;
direction: Direction; direction: Direction;
selectedNodeIsRoot: boolean; selectedNodeIsRoot: boolean;
@@ -26,344 +29,240 @@ type Props = {
onAdd?: () => void; onAdd?: () => void;
}; };
export default class Editor extends Component<Props> { export default function Editor(props: Props) {
componentDidMount() { const {node, selectedNodeIsRoot} = props;
document.addEventListener('keydown', this.onKeyDown); const disabled = node == null;
}
componentWillUnmount() { return (
document.removeEventListener('keydown', this.onKeyDown); <div className={styles.editor}>
} <Tabs block={true}>
<TabItem
value="flex"
label="Flex"
className={styles.tabItem}
default={true}>
<h2>Direction</h2>
<EditValue
property="direction"
value={props.direction}
onChange={props.onChangeSetting}
/>
<h2>Flex Direction</h2>
<EditValue
disabled={disabled}
property="flexDirection"
value={node ? node.flexDirection : undefined}
onChange={props.onChangeLayout}
/>
onKeyDown = (e: KeyboardEvent) => { <div className="row margin--none">
if ( <div className="col col--4">
(e.key === 'Delete' || e.key === 'Backspace') && <h2>Basis</h2>
this.props.onRemove &&
!(e.target instanceof HTMLInputElement)
) {
this.props.onRemove();
}
};
render() {
const {node, selectedNodeIsRoot} = this.props;
const disabled = !node;
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>
<EditValue
property="direction"
value={this.props.direction}
onChange={this.props.onChangeSetting}
/>
<h2>
Flex Direction
<InfoText doclink="/docs/flex-direction">
Defines the direction of the main-axis
</InfoText>
</h2>
<EditValue
disabled={disabled}
property="flexDirection"
value={node ? node.flexDirection : undefined}
onChange={this.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>
<EditValue
// @ts-ignore
type="text"
property="flexBasis"
placeholder="auto"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexBasis : undefined}
onChange={this.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>
<EditValue
// @ts-ignore
type="text"
property="flexGrow"
placeholder="0"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexGrow : undefined}
onChange={this.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>
<EditValue
// @ts-ignore
type="text"
property="flexShrink"
placeholder="1"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexShrink : undefined}
onChange={this.props.onChangeLayout}
/>
</Col>
</Row>
<h2>
Flex Wrap
<InfoText doclink="/docs/flex-wrap">
Wrapping behaviour when child nodes don't fit into a single line
</InfoText>
</h2>
<EditValue
disabled={disabled}
property="flexWrap"
value={node ? node.flexWrap : undefined}
onChange={this.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>
<EditValue
disabled={disabled}
property="justifyContent"
value={node ? node.justifyContent : undefined}
onChange={this.props.onChangeLayout}
/>
<h2>
Align Items
<InfoText doclink="/docs/align-items">
Aligns child nodes along the cross-axis
</InfoText>
</h2>
<EditValue
disabled={disabled}
property="alignItems"
value={node ? node.alignItems : undefined}
onChange={this.props.onChangeLayout}
/>
<h2>
Align Self
<InfoText doclink="/docs/align-items">
Override align items of parent
</InfoText>
</h2>
<EditValue
disabled={disabled || selectedNodeIsRoot}
property="alignSelf"
value={node ? node.alignSelf : undefined}
onChange={this.props.onChangeLayout}
/>
<h2>
Align Content
<InfoText doclink="/docs/align-content">
Alignment of lines along the cross-axis when wrapping
</InfoText>
</h2>
<EditValue
disabled={disabled}
property="alignContent"
value={node ? node.alignContent : undefined}
onChange={this.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}>
<EditValue
// @ts-ignore
type="text"
placeholder="auto"
property="width"
disabled={disabled}
value={node ? node.width : undefined}
onChange={this.props.onChangeLayout}
/>
</Col>
<Col span={12}>
<EditValue
// @ts-ignore
type="text"
placeholder="auto"
property="height"
disabled={disabled}
value={node ? node.height : undefined}
onChange={this.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}>
<EditValue
// @ts-ignore
type="text"
placeholder="none"
property="maxWidth"
disabled={disabled}
value={node ? node.maxWidth : undefined}
onChange={this.props.onChangeLayout}
/>
</Col>
<Col span={12}>
<EditValue
// @ts-ignore
type="text"
placeholder="none"
property="maxHeight"
disabled={disabled}
value={node ? node.maxHeight : undefined}
onChange={this.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}>
<EditValue
// @ts-ignore
type="text"
placeholder="0"
property="minWidth"
disabled={disabled}
value={node ? node.minWidth : undefined}
onChange={this.props.onChangeLayout}
/>
</Col>
<Col span={12}>
<EditValue
// @ts-ignore
type="text"
placeholder="0"
property="minHeight"
disabled={disabled}
value={node ? node.minHeight : undefined}
onChange={this.props.onChangeLayout}
/>
</Col>
</Row>
<h2>
Aspect Ratio
<InfoText doclink="/docs/aspect-ratio">
Width/Height aspect ratio of node
</InfoText>
</h2>
<EditValue
// @ts-ignore
type="text"
placeholder="auto"
property="aspectRatio"
disabled={disabled}
value={node ? node.aspectRatio : undefined}
onChange={this.props.onChangeLayout}
/>
{['padding', 'border', 'margin'].map(property => (
<EditValue <EditValue
property={property} // @ts-ignore
key={property} type="text"
value={node ? node[property] : undefined} property="flexBasis"
onChange={this.props.onChangeLayout} placeholder="auto"
disabled={property === 'margin' && selectedNodeIsRoot} disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexBasis : undefined}
onChange={props.onChangeLayout}
/> />
))} </div>
<h2> <div className="col col--4">
Position Type <h2>Grow</h2>
<InfoText doclink="/docs/absolute-relative-layout"> <EditValue
Relative position offsets the node from it's calculated // @ts-ignore
position. Absolute position removes the node from the flexbox type="text"
flow and positions it at the given position. property="flexGrow margin--none"
</InfoText> placeholder="0"
</h2> disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexGrow : undefined}
onChange={props.onChangeLayout}
/>
</div>
<div className="col col--4">
<h2>Shrink</h2>
<EditValue
// @ts-ignore
type="text"
property="flexShrink"
placeholder="1"
disabled={disabled || selectedNodeIsRoot}
value={node ? node.flexShrink : undefined}
onChange={props.onChangeLayout}
/>
</div>
</div>
<EditValue <h2>Flex Wrap</h2>
disabled={disabled || selectedNodeIsRoot} <EditValue
property="positionType" disabled={disabled}
value={node ? node.positionType : undefined} property="flexWrap"
onChange={this.props.onChangeLayout} value={node ? node.flexWrap : undefined}
/> onChange={props.onChangeLayout}
<EditValue />
disabled={selectedNodeIsRoot} </TabItem>
property="position" <TabItem value="alignment" label="Alignment" className={styles.tabItem}>
value={node ? node.position : undefined} <h2>Justify Content</h2>
onChange={this.props.onChangeLayout} <EditValue
/> disabled={disabled}
</TabPane> property="justifyContent"
</Tabs> value={node ? node.justifyContent : undefined}
onChange={props.onChangeLayout}
/>
<Row gutter={15} className="EditorButtons"> <h2>Align Items</h2>
<Col span={12}> <EditValue
<Button disabled={disabled}
icon="plus-circle-o" property="alignItems"
disabled={!this.props.onAdd} value={node ? node.alignItems : undefined}
onClick={this.props.onAdd} onChange={props.onChangeLayout}
type="primary"> />
add child node
</Button> <h2>Align Self</h2>
</Col> <EditValue
<Col span={12}> disabled={disabled || selectedNodeIsRoot}
<Button property="alignSelf"
icon="close-circle-o" value={node ? node.alignSelf : undefined}
disabled={!this.props.onRemove} onChange={props.onChangeLayout}
onClick={this.props.onRemove} />
type="danger">
remove node <h2>Align Content</h2>
</Button> <EditValue
</Col> disabled={disabled}
</Row> property="alignContent"
value={node ? node.alignContent : undefined}
onChange={props.onChangeLayout}
/>
</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"
placeholder="auto"
property="width"
disabled={disabled}
value={node ? node.width : undefined}
onChange={props.onChangeLayout}
/>
</div>
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
placeholder="auto"
property="height"
disabled={disabled}
value={node ? node.height : undefined}
onChange={props.onChangeLayout}
/>
</div>
</div>
<h2>Max-Width &times; Max-Height</h2>
<div className="row margin--none">
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
placeholder="none"
property="maxWidth"
disabled={disabled}
value={node ? node.maxWidth : undefined}
onChange={props.onChangeLayout}
/>
</div>
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
placeholder="none"
property="maxHeight"
disabled={disabled}
value={node ? node.maxHeight : undefined}
onChange={props.onChangeLayout}
/>
</div>
</div>
<h2>Min-Width &times; Min-Height</h2>
<div className="row margin--none">
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
placeholder="0"
property="minWidth"
disabled={disabled}
value={node ? node.minWidth : undefined}
onChange={props.onChangeLayout}
/>
</div>
<div className="col col--6">
<EditValue
// @ts-ignore
type="text"
placeholder="0"
property="minHeight"
disabled={disabled}
value={node ? node.minHeight : undefined}
onChange={props.onChangeLayout}
/>
</div>
</div>
<h2>Aspect Ratio</h2>
<EditValue
// @ts-ignore
type="text"
placeholder="auto"
property="aspectRatio"
disabled={disabled}
value={node ? node.aspectRatio : undefined}
onChange={props.onChangeLayout}
/>
{['padding', 'border', 'margin'].map(property => (
<EditValue
property={property}
key={property}
value={node ? node[property] : undefined}
onChange={props.onChangeLayout}
disabled={property === 'margin' && selectedNodeIsRoot}
/>
))}
<h2>Position Type</h2>
<EditValue
disabled={disabled || selectedNodeIsRoot}
property="positionType"
value={node ? node.positionType : undefined}
onChange={props.onChangeLayout}
/>
<EditValue
disabled={selectedNodeIsRoot}
property="position"
value={node ? node.position : undefined}
onChange={props.onChangeLayout}
/>
</TabItem>
</Tabs>
<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>
); </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 LayoutRecord from './LayoutRecord';
import Sidebar from './Sidebar'; import Sidebar from './Sidebar';
import type {LayoutRecordType} from './LayoutRecord'; import type {LayoutRecordType} from './LayoutRecord';
import './index.css'; import styles from './Playground.module.css';
import clsx from 'clsx';
type Props = { type Props = {
layoutDefinition: LayoutRecordType; layoutDefinition?: LayoutRecordType;
direction: Direction; direction?: Direction;
maxDepth: number; maxDepth?: number;
maxChildren?: number; maxChildren?: number;
minChildren?: number; minChildren?: number;
selectedNodePath?: Array<number>; selectedNodePath?: Array<number>;
showGuides: boolean; showGuides?: boolean;
className?: string; className?: string;
height?: string | number; height?: string | number;
persist?: boolean; persist?: boolean;
renderSidebar?: ( renderSidebar?: (
layoutDefinition: LayoutRecordType, layoutDefinition: LayoutRecordType,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange: () => any, onChange: () => any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => 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) => { onChangeLayout = (key: string, value: any) => {
const {selectedNodePath} = this.state; const {selectedNodePath} = this.state;
if (selectedNodePath) { if (selectedNodePath) {
@@ -158,7 +162,9 @@ export default class Playground extends Component<Props, State> {
}; };
modifyAtPath( modifyAtPath(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
path: Array<any>, path: Array<any>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any, value: any,
selectedNodePath: Array<number> = this.state.selectedNodePath, selectedNodePath: Array<number> = this.state.selectedNodePath,
) { ) {
@@ -235,76 +241,60 @@ export default class Playground extends Component<Props, State> {
: null; : null;
const playground = ( const playground = (
<div className="playground-background"> <div
<div className={styles.playground}
className={`Playground ${ onMouseDown={this.onMouseDown}
this.props.renderSidebar ? '' : 'standalone' style={{height, maxHeight: height}}
}`} ref={ref => {
onMouseDown={this.onMouseDown} this._containerRef = ref;
style={{height, maxHeight: height}} }}>
ref={ref => { <YogaNode
this._containerRef = ref; layoutDefinition={layoutDefinition}
}}> selectedNodePath={selectedNodePath}
<YogaNode onClick={selectedNodePath => this.setState({selectedNodePath})}
layoutDefinition={layoutDefinition} onDoubleClick={this.onAdd}
selectedNodePath={selectedNodePath} direction={direction}
onClick={selectedNodePath => this.setState({selectedNodePath})} showGuides={this.props.showGuides}
onDoubleClick={this.onAdd} />
direction={direction}
showGuides={this.props.showGuides}
/>
{!this.props.renderSidebar && (
<Sidebar>
{this.state.selectedNodePath ? (
<Editor
node={selectedNode}
selectedNodeIsRoot={
selectedNodePath ? selectedNodePath.length === 0 : false
}
onChangeLayout={this.onChangeLayout}
// @ts-ignore
onChangeSetting={(key, value) =>
this.setState({[key]: value})
}
direction={direction}
onRemove={
selectedNodePath && selectedNodePath.length > 0
? this.onRemove
: undefined
}
onAdd={
selectedNodePath &&
selectedNodePath.length < this.props.maxDepth
? this.onAdd
: undefined
}
/>
) : (
<div className="NoContent">
Select a node to edit its properties
</div>
)}
</Sidebar>
)}
</div>
</div> </div>
); );
if (this.props.renderSidebar) { const sidebarContent = this.props.renderSidebar
return ( ? this.props.renderSidebar(
<div className={`PlaygroundContainer ${this.props.className || ''}`}> // @ts-ignore
<div> layoutDefinition.getIn(getPath(selectedNodePath)),
{this.props.renderSidebar( this.onChangeLayout,
)
: this.state.selectedNodePath != null && (
<Editor
node={selectedNode}
selectedNodeIsRoot={
selectedNodePath ? selectedNodePath.length === 0 : false
}
onChangeLayout={this.onChangeLayout}
onChangeSetting={(key, value) =>
// @ts-ignore // @ts-ignore
layoutDefinition.getIn(getPath(selectedNodePath)), this.setState({[key]: value})
this.onChangeLayout, }
)} direction={direction}
</div> onRemove={
{playground} selectedNodePath && selectedNodePath.length > 0
</div> ? this.onRemove
); : undefined
} else { }
return playground; onAdd={
} selectedNodePath && selectedNodePath.length < this.props.maxDepth
? this.onAdd
: undefined
}
/>
);
return (
<div className={clsx(styles.container, this.props.className)}>
{playground}
<Sidebar>{sidebarContent}</Sidebar>
</div>
);
} }
} }

View File

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

View File

@@ -8,18 +8,30 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import './Sidebar.css'; import styles from './Sidebar.module.css';
import clsx from 'clsx';
type Props = { type Props = {
width?: number; width?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
children: 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> { export default class Sidebar extends Component<Props> {
render() { render() {
return ( return (
<div className="Sidebar" style={{width: this.props.width}}> <div
{this.props.children} className={clsx('card', styles.sidebar)}
style={{width: this.props.width}}>
{this.props.children || <PlaceholderContent />}
</div> </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 React, {Component} from 'react';
import clsx from 'clsx';
import Yoga from 'yoga-layout'; import Yoga from 'yoga-layout';
import {Radio, Menu, Dropdown, Button, Icon} from 'antd';
import './YogaEnumSelect.css'; import styles from './YogaEnumSelect.module.css';
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
const PROPERTY_LOOKUP = { const PROPERTY_LOOKUP = {
flexDirection: 'FLEX_DIRECTION', flexDirection: 'FLEX_DIRECTION',
@@ -65,41 +64,30 @@ export default class YogaEnumSelect extends Component<Props> {
const selected = this.values.find(({value}) => value === this.props.value); const selected = this.values.find(({value}) => value === this.props.value);
return this.values.length > 3 ? ( return this.values.length > 3 ? (
<div className="YogaEnumSelect"> <select className={styles.select} name={this.props.property}>
{/*@ts-ignore*/}
<Dropdown
trigger={['click']}
disabled={this.props.disabled}
overlay={
// @ts-ignore
<Menu onClick={this.handleMenuClick}>
{this.values.map(({key, value}) => (
// @ts-ignore
<Menu.Item key={key} value={value}>
{this.getTitle(property, key)}
</Menu.Item>
))}
</Menu>
}>
<Button>
{selected ? this.getTitle(property, selected.key) : ''}
{/*@ts-ignore*/}
<Icon type="down" />
</Button>
</Dropdown>
</div>
) : (
<RadioGroup
{...this.props}
onChange={e => this.props.onChange(this.props.property, e.target.value)}
defaultValue="a"
className="YogaEnumSelect">
{this.values.map(({key, value}) => ( {this.values.map(({key, value}) => (
<RadioButton key={key} value={value}> <option key={key} value={value}>
{this.getTitle(property, key)} {selected ? this.getTitle(property, key) : ''}
</RadioButton> </option>
))} ))}
</RadioGroup> </select>
) : (
<div className={clsx('button-group', styles.buttonGroup)}>
{this.values.map(({key, 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)}
</button>
))}
</div>
); );
} }
} }

View File

@@ -5,17 +5,30 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
.YogaNode { .YogaNode {
background: white; box-sizing: border-box;
background: var(--ifm-background-surface-color);
position: absolute; position: absolute;
transform: scale(1); transform: scale(1);
box-shadow: inset 0 0 0px 1px rgba(48, 56, 69, 0.2); box-shadow: var(--ifm-global-shadow-lw);
transition: 0.2s all, 0s outline, 0s box-shadow;
cursor: pointer; cursor: pointer;
animation: yoga-node-fadein 200ms ease;
} }
.YogaNode.hover { @keyframes yoga-node-fadein {
background-color: #F0FFF9; 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 { .YogaNode .YogaNode {
@@ -26,13 +39,8 @@
background: rgba(240, 255, 249, 0.7); background: rgba(240, 255, 249, 0.7);
} }
.YogaNode:focus {
outline: 0;
}
.YogaNode.focused { .YogaNode.focused {
box-shadow: 0 0 0 2px #68CFBB, 0 0 15px rgba(0, 0, 0, 0.2), outline: 2px solid var(--ifm-color-primary);
inset 0 0 0px 1px rgba(255, 255, 255, 0.2);
z-index: 2; z-index: 2;
} }

View File

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

View File

@@ -8,7 +8,6 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Input} from 'antd';
import PositionRecord from './PositionRecord'; import PositionRecord from './PositionRecord';
import type {PositionRecordType} from './PositionRecord'; import type {PositionRecordType} from './PositionRecord';
import './YogaPositionEditor.css'; import './YogaPositionEditor.css';
@@ -33,14 +32,14 @@ export default class YogaPositionEditor extends Component<Props> {
const {onChange, value, property, disabled} = this.props; const {onChange, value, property, disabled} = this.props;
return ( return (
<div className="YogaPositionEditor"> <div className="YogaPositionEditor">
<Input <input
type="text" type="text"
value={Number.isNaN(value.top) ? '' : value.top} value={Number.isNaN(value.top) ? '' : value.top}
disabled={disabled} disabled={disabled}
onChange={e => onChange(property, value.set('top', e.target.value))} onChange={e => onChange(property, value.set('top', e.target.value))}
/> />
<div className="YogaPositionEditorRow"> <div className="YogaPositionEditorRow">
<Input <input
type="text" type="text"
value={Number.isNaN(value.left) ? '' : value.left} value={Number.isNaN(value.left) ? '' : value.left}
disabled={disabled} disabled={disabled}
@@ -49,7 +48,7 @@ export default class YogaPositionEditor extends Component<Props> {
} }
/> />
{property.toUpperCase()} {property.toUpperCase()}
<Input <input
type="text" type="text"
value={Number.isNaN(value.right) ? '' : value.right} value={Number.isNaN(value.right) ? '' : value.right}
disabled={disabled} disabled={disabled}
@@ -58,7 +57,7 @@ export default class YogaPositionEditor extends Component<Props> {
} }
/> />
</div> </div>
<Input <input
type="text" type="text"
value={Number.isNaN(value.bottom) ? '' : value.bottom} value={Number.isNaN(value.bottom) ? '' : value.bottom}
disabled={disabled} 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. */ /* You can override the default Infima variables here. */
:root { :root {
--ifm-color-primary: #2e8555; --ifm-color-primary-lightest: rgb(99, 183, 168);
--ifm-color-primary-dark: #29784c; --ifm-color-primary-lighter: rgb(70, 159, 143);
--ifm-color-primary-darker: #277148; --ifm-color-primary-light: rgb(48, 135, 119);
--ifm-color-primary-darkest: #205d3b; --ifm-color-primary: rgb(33, 111, 97);
--ifm-color-primary-light: #33925d; --ifm-color-primary-dark: rgb(22, 87, 75);
--ifm-color-primary-lighter: #359962; --ifm-color-primary-darker: rgb(14, 63, 54);
--ifm-color-primary-lightest: #3cad6e; --ifm-color-primary-darkest: rgb(8, 39, 33);
--ifm-code-font-size: 95%; --ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
} }
/* For readability concerns, you should choose a lighter palette in dark mode. */ /* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] { [data-theme='dark'] {
--ifm-color-primary: #25c2a0; --ifm-color-primary-lightest: rgb(192, 231, 224);
--ifm-color-primary-dark: #21af90; --ifm-color-primary-lighter: rgb(146, 207, 196);
--ifm-color-primary-darker: #1fa588; --ifm-color-primary-light: rgb(106, 183, 169);
--ifm-color-primary-darkest: #1a8870; --ifm-color-primary: rgb(74, 159, 144);
--ifm-color-primary-light: #29d5b0; --ifm-color-primary-dark: rgb(51, 135, 120);
--ifm-color-primary-lighter: #32d8b4; --ifm-color-primary-darker: rgb(34, 111, 97);
--ifm-color-primary-lightest: #4fddbf; --ifm-color-primary-darkest: rgb(22, 87, 75);
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
} }

View File

@@ -10,47 +10,151 @@
* and scoped locally. * and scoped locally.
*/ */
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);
}
.heroBanner { .heroBanner {
padding: 4rem 0; flex: 1;
text-align: center; display: flex;
position: relative; flex-direction: row;
overflow: hidden; justify-content: center;
} }
@media screen and (max-width: 996px) { .heroRow {
.heroBanner { align-items: center;
padding: 2rem;
}
} }
.buttons { .blueprintColumn {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.playgroundFallback { @media (max-width: 996px) {
height: 500px; .blueprintColumn {
width: 100%; display: none;
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), .blueprint {
linear-gradient( --blueprint-gap: 5%;
transparent 4px, --fadein-duration: 500ms;
#f5f5f5 4px, box-shadow: var(--ifm-global-shadow-tl);
#f5f5f5 97px, background-color: var(--ifm-background-surface-color);
transparent 97px border-radius: 0.5rem;
), }
linear-gradient(-90deg, #e5e5e5 1px, transparent 1px),
linear-gradient(
-90deg, .blueprintContainer {
transparent 4px, position: relative;
#f5f5f5 4px, width: var(--ifm-col-width);
#f5f5f5 97px, aspect-ratio: 1.0;
transparent 97px background-color: var(--ifm-color-primary-lighter);
), box-shadow: var(--ifm-global-shadow-lw);
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; .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 React, {Suspense} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import BrowserOnly from '@docusaurus/BrowserOnly'; import BrowserOnly from '@docusaurus/BrowserOnly';
import styles from './index.module.css'; import styles from './index.module.css';
function HomepageHeader() { function HeroSection() {
const {siteConfig} = useDocusaurusContext();
return ( return (
<header className={clsx('hero hero--primary', styles.heroBanner)}> <header className={clsx('hero', styles.heroBanner)}>
<div className="container"> <div className={clsx('row', 'container', styles.heroRow)}>
<h1 className="hero__title">{siteConfig.title}</h1> <div className="col col--6">
<p className="hero__subtitle">{siteConfig.tagline}</p> <h1 className="hero__title">Yoga Layout</h1>
<div className={styles.buttons}> <p className="hero__subtitle">
<Link A portable and perfomant layout engine targeting web standards
className="button button--secondary button--lg" </p>
to="/docs/intro">
Docusaurus Tutorial - 5min <Link className="button button--primary button--lg" to="/docs/intro">
Learn more
</Link> </Link>
</div> </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> </div>
</header> </header>
); );
} }
const LazyPlayground = React.lazy(() => import('../components/Playground')); const LazyPlayground = React.lazy(
() => import('../components/Playground/Playground'),
function ClientPlayground() { );
const fallback = <div className={styles.playgroundFallback} />;
// 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 ( return (
<BrowserOnly fallback={fallback}> <BrowserOnly fallback={null}>
{() => ( {() => (
<Suspense fallback={fallback}> <Suspense fallback={null}>
<LazyPlayground /> <LazyPlayground className={styles.playground} />
</Suspense> </Suspense>
)} )}
</BrowserOnly> </BrowserOnly>
); );
} }
export default function Home(): JSX.Element { function PlaygroundSection() {
const {siteConfig} = useDocusaurusContext();
return ( return (
<Layout <main className={styles.playgroundSection}>
title={`Hello from ${siteConfig.title}`} <div className="container">
description="Description will go into a meta tag in <head />"> <BrowserOnlyPlayground />
<HomepageHeader /> </div>
<main> </main>
<HomepageFeatures /> );
<ClientPlayground /> }
</main>
export default function Home(): JSX.Element {
return (
<Layout title="Yoga Layout | A cross-platform layout engine">
<HeroSection />
<PlaygroundSection />
</Layout> </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. // 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": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"target": "esnext", "target": "esnext",

4763
yarn.lock

File diff suppressed because it is too large Load Diff