Code Monkey home page Code Monkey logo

radium-constraints's Introduction

Radium Constraints Build Status Coverage Status

Radium Constraints introduces the power of constraint-based layout to React. Declare simple relationships between your visual components and let the constraint solver generate the optimum layout.

Radium Constraints handles DOM and SVG elements and is the perfect alternative to manual layout when building SVG data visualizations. Radium Constraints is the bedrock for exciting new enhancements to Victory.

Pre-alpha

This library is still incubating and is not yet ready for production use. See the roadmap for what's left to do before 1.0.0.

Usage

First, ensure all components using Radium Constraints are wrapped in the top-level <ConstraintLayout> component:

import ConstraintLayout from "radium-constraints";

<ConstraintLayout>
  ...other components
</ConstraintLayout>

Next, add a <Superview> component. A <Superview> is a collection of "subviews" whose layouts relate to each other and their parent. A <Superview> typically encapsulates one large visual component, like a single chart.

Superviews require the following props:

  • name: for identification. We'll remove this requirement in future versions.
  • container which element the superview should use as a container (i.e. div for DOM and g for SVG).
  • width: the initial width of the superview.
  • height: the initial height of the superview.
  • style (optional) custom styles to apply to the container node.

Here's how to set up a <Superview>:

import ConstraintLayout, { Superview } from "radium-constraints";

<ConstraintLayout>
  <Superview
    name="tutorial"
    container="div"
    width={400}
    height={500}
    style={{
      background: "red"
    }}
  >
    ...subview components
  </Superview>
</ConstraintLayout>

Finally, add subviews to your <Superview>! You can create subviews in two ways. The first, AutoDOM and AutoSVG, automatically map the bounding box of the subview to the appropriate DOM styles/SVG attributes. AutoDOM uses the bounding box to absolutely position the element. AutoSVG maps the bounding box to attributes like x, y, x1, y1, cx, cy, r, etc. on a per-element basis.

If you need more control over the usage of the bounding box in components, you can create a custom subview using the <Subview> higher-order component. <Subview> provides layout props (width, height, top, left, bottom, right) that you can map to DOM attributes or style props.

Both of these methods use <Subview> under the covers, and they require the same props:

  • name: the name of the subview. Allows other subviews to constrain themselves to this subview.
  • intrinsicWidth: the minimum width of the component before application of constraints.
  • intrinsicHeight: the minimum height of the component before application of constraints.
  • constraints: a set of constraints for this subview.

Building constraints uses a fluent DSL in the style of Chai assertions. Some constraint examples:

// These two constraints center the subview in the <Superview>.
constrain.subview("demo").centerX.to.equal.superview.centerX
constrain.subview("demo").centerY.to.equal.superview.centerY

// This prevents this subview from overlapping with
// another subview named "other".
constrain.subview("demo").left
  .to.be.greaterThanOrEqualTo.subview("other").right

// This prevents this subview from overflowing
// the superview's right edge.
constrain.subview("demo").right
  .to.be.lessThanOrEqualTo.superview.right

Here's an example of a custom component using <Subview>:

class Rectangle extends Component {
  static propTypes = {
    layout: PropTypes.shape({
      top: PropTypes.number,
      right: PropTypes.number,
      bottom: PropTypes.number,
      left: PropTypes.number
    })
  };

  render() {
    return (
      <div style={{
        position: "absolute",
        top: this.props.layout.top || 0,
        left: this.props.layout.left || 0
        width: this.props.layout.width || 0,
        height: this.props.layout.height || 0
      }}
      >
        <p>Demo time!</p>
      </div>
    );
  }
}

export default Subview()(Rectangle);

Here's how to use AutoDOM components:

import ConstraintLayout, { Superview, AutoDOM } from "radium-constraints";

<ConstraintLayout>
  <Superview
    name="tutorial"
    container="div"
    width={400}
    height={500}
    style={{
      background: "red"
    }}
  >
    <AutoDOM.div
      name="tutorialSubview"
      intrinsicWidth={50}
      intrinsicHeight={50}
      constraints={[
        constrain.subview("tutorialSubview").centerX
          .to.equal.superview.centerX,
        constrain.subview("tutorialSubview").centerY
          .to.equal.superview.centerY
      ]}
    >
      This is a subview
    </AutoDOM.div>
  </Superview>
</ConstraintLayout>

When using AutoSVG components, make sure to pass "g" instead of "div" to the <Superview>'s container prop.

Animation

You can add automatic layout animation to any Subview or AutoSVG/AutoDOM components! The animation system works with both <Motion> from react-motion and <VictoryAnimation> from victory-core. To create Victory-animated versions of AutoDOM components, for example, you'd do the following:

import { animateDOM } from "radium-constraints";
import { VictoryAnimation } from "victory-core";

const VictoryAnimationAutoDOM = animateDOM({
  animatorClass: VictoryAnimation,
  animatorProps: (layout) => ({
    data: {
      width: layout.width,
      height: layout.height,
      top: layout.top,
      right: layout.right,
      bottom: layout.bottom,
      left: layout.left
    }
  })
});

// Later, in render()
<VictoryAnimationAutoDOM.p
  name="victory-animation-note"
  style={{...styles.box, fontSize: "16px", border: 0}}
  intrinsicWidth={300}
  intrinsicHeight={45}
  constraints={this.state.dynamicConstraints}
>
  This is a subview animated by VictoryAnimation!!!!!!
</VictoryAnimationAutoDOM.p>

When different constraints enter either the top-level or component-level constraints prop, the new animated component automatically tweens between the previous and newly calculated layout, diffing/removing/adding constraints behind the scenes.

If you're using the Subview higher-order component, you can pass an object with animatorClass and animatorProps to the first curried argument of Subview like so:

export default Subview({
  animatorClass: VictoryAnimation,
  animatorProps: (layout) => ({
    data: {
      width: layout.width,
      height: layout.height,
      top: layout.top,
      right: layout.right,
      bottom: layout.bottom,
      left: layout.left
    }
  })
})(SomeCustomComponent);

Demo

There are more complex examples on the demo page. Check out the code in app.jsx.

Installation

  • Clone this repo
  • npm install and then ./node_modules/.bin/builder run hot will load a webpack dev server at localhost:3000.

If you want to type builder instead of ./node_modules/.bin/builder, please update your shell to include ./node_modules/.bin in PATH like:

export PATH="${PATH}:./node_modules/.bin"

Caveats

React Constraints uses an asynchronous layout engine running on a pool of WebWorkers. This prevents layout calculations from bogging down the main thread and allows the library to be a good citizen in any page/app. Therefore, browsers using this library must support WebWorkers.

Resolving and incrementally adding/removing constraints are cheap enough to run in 60fps for most cases. However, the initial layout calculations on first load are the most expensive, and you may notice a slight delay in layout (although this does not block the main thread). We're working on a build tool that will pre-calculate initial layouts and feed them into your components to prevent this.

Browser support

This library's browser support aligns with React's browser support minus IE 8 and 9 (neither support Web Workers). The library requires a Promise polyfill for non-ES6 environments.

Roadmap

In order of priority:

  • Create build tool to pre-calculate initial layouts.
  • Support SVG path elements in AutoSVG.
  • Remove dependency on autolayout.js in favor of a simple wrapper around the Kiwi constraint solver.
  • Allow for self-referential subviews in the constraint props array without using the subview string.

Constraint Builder API

constrain

Begins a constraint builder chain.

subview(name)

Chooses a subview by name to pick attributes from.

superview

Chooses the superview to pick attributes from.

to and be

No-op methods. Use with lessThanOrEqualTo and greaterThanOrEqualTo for legibility.

equal

Declare that the subview or superview attribute on the left will equal the subview or superview attribute on the right.

lessThanOrEqualTo and greaterThanOrEqualTo

Declare that the subview or superview attribute on the left will be greater than/less than or equal to the subview or superview attribute on the right.

width, height, top, left, bottom, right, centerX, centerY

The constrainable attributes of a subview or superview. Call these methods on superview or subview(name) to complete one side of a constraint relationship.

constant

Declare that the subview or superview attribute on the left will equal a constant number (no relation to another subview/superview).

plus, minus, times

Modifies an attribute by a constant. Call these methods on attributes of superview or subview, like superview.left.plus(10).

withPriority(priority)

Declare the priority of this constraint. Accepts 1000, 750, and 250 as values. Use at the end of the entire chain.

Examples

constrain.subview("demo").bottom
  .to.equal.superview.top.plus(10)
  .withPriority(1000);
  
constrain.subview("demo").bottom.constant(20);

constrain.subview("demo").right
  .to.be.lessThanOrEqualTo.superview.right

radium-constraints's People

Contributors

tptee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

radium-constraints's Issues

Rename `<View>` to `<Superview>`

This avoids conflict with react-native and react-native-web while also clarifying the role of superview in the constraint builder.

This will require doc changes!

Allow for self-referential subviews in the constraint props array

It's redundant that we have to specify the current subview name in the constraint builders:

<AutoDOM.div
  name="tutorialSubview"
  intrinsicWidth={50}
  intrinsicHeight={50}
  constraints={[
    constrain().subview("tutorialSubview").centerX
      .to.equal.superview.centerX,
    constrain().subview("tutorialSubview").centerY
      .to.equal.superview.centerY
  ]}
 >
  This is a subview
</AutoDOM.div>

This is preferable (still allowing references to other subviews):

<AutoDOM.div
  name="tutorialSubview"
  intrinsicWidth={50}
  intrinsicHeight={50}
  constraints={[
    constrain().thisSubview.centerX
      .to.equal.superview.centerX,
    constrain().thisSubview.centerY
      .to.equal.superview.centerY
  ]}
 >
  This is a subview
</AutoDOM.div>

Fix build on node 0.10.x

Relevant Travis logs:

ERROR in ./src/layout-client.js
Module build failed: TypeError: /home/travis/build/FormidableLabs/radium-constraints/src/layout-client.js: undefined is not a function
    at exports.default (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-plugin-webpack-loaders/lib/runWebPackSync.js:41:51)
    at PluginPass.StringLiteral (/home/travis/build/FormidableLabs/radium-constraints/plugins/lib/babel-plugin-inline-worker.js:41:59)
    at newFn (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/visitors.js:262:19)
    at NodePath._call (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/path/context.js:63:18)
    at NodePath.call (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/path/context.js:47:17)
    at NodePath.visit (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/path/context.js:93:12)
    at TraversalContext.visitQueue (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/context.js:152:16)
    at TraversalContext.visitMultiple (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/context.js:110:17)
    at TraversalContext.visit (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/context.js:182:19)
    at Function.traverse.node (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/index.js:135:17)
    at NodePath.visit (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/path/context.js:103:22)
    at TraversalContext.visitQueue (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/context.js:152:16)
    at TraversalContext.visitMultiple (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/context.js:110:17)
    at TraversalContext.visit (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/context.js:182:19)
    at Function.traverse.node (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/index.js:135:17)
    at NodePath.visit (/home/travis/build/FormidableLabs/radium-constraints/node_modules/babel-traverse/lib/path/context.js:103:22)
 @ ./src/constraint-layout.js 34:216-242

Has something to do with our little Frankenstein Babel plugin.

Implement animations

This will require the ability to remove constraints, which Autolayout.js does not provide.

Pre-calculate initial layout

In the README it says:

We're working on a build tool that will pre-calculate initial layouts and feed them into your components to prevent this

I think this may have just been solved by @jaredly. Would you be willing to accept a PR with this integrated? Please let me know, I'd love to try and see if they can work together.

Write a slimmer alternative to Autolayout.js

The Autolayout.js bundle size is HUGE due to the VFL parser we don't need. Furthermore, the npm build of Autolayout.js is tied to Cassowary.js, which we want to switch out with kiwi. The parts we do need should be easy to replicate.

We'll also need to add support for removing constraints, which seems trivial.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.