Home

Awesome

control-panel

NPM version experimental

This is a React port of control-panel that aims to replicate the functionality of the original exactly while making it easily usable from React. All of the features of the original have been brought over, and the API remains very similar.

Embeddable panel of inputs for adding parameter selection to your app or visualization. Modern and minimalist design. Fully encapsulated module including JS and CSS. Can easily be added to any app or page. Heavily inspired by dat-gui, but streamlined, simplified, and written as a npm module.

LIVE DEMO

themes


Supports the following input types

rangecheckboxtextcolorbuttonintervalselectcustom


Includes the following themes

darklight

Want to contribute a new theme or input type? Submit a PR!

install

Add to your project with

npm install control-panel

The UI uses the Hack font which users may not have installed on their machines locally. To include a version dynamically, add the following line to your <head>:

<style type="text/css" rel="stylesheet"//cdn.jsdelivr.net/font-hack/2.019/css/hack.min.css></style>

example

Create a panel with four elements and add to your page in the top right.

import ControlPanel, {
  Button,
  Checkbox,
  Multibox,
  Select,
  Text,
  Color,
  Range,
  Interval,
  Custom,
} from 'react-control-panel';

const initialState = {
  'range slider': 20,
  'stepped slider': 0.6,
  interval: [25, 50],
  text: 'my setting',
  checkbox: true,
  'color rgb': 'rgb(100, 200, 100',
  'color hex': '#30b2ba',
  selection: 'option 1',
  'multiple checkboxes': [true, true],
};

const DemoPanel = () => (
  <ControlPanel
    theme='dark'
    title='Demo Panel'
    initialState={initialState}
    onChange={console.log}
    width={500}
    style={{ marginRight: 30 }}
  >
    <Range label='range slider' min={0} max={100} />
    <Range label='stepped slider' min={0} max={1} />
    <Interval label='interval' min={0} max={100} />
    <Text label='text' />
    <Checkbox label='checkbox' />
    <Color label='color rgb' format='rgb' />
    <Color label='color hex' format='hex' />
    <Button label='gimme an alert' action={() => alert('clicked')} disabled={false} />
    <Select label='selection' options={{ 'option 1': 1, 'option 2': 2 }} />
    <Multibox
      label='multiple checkboxes'
      colors={['rgb(100,120,230)', 'rgb(210,100,190)']}
      names={['box1', 'box2']}
    />
    <Custom
      Comp={({ value, onChange, theme }) => (
        <MyCustomComponent value={value} onChange={onChange} theme={theme} />
      )}
    />
  </ControlPanel>
);

classic setting definitions

It's also possible to use the old array-based definition system from the original version:

<DemoPanel
  theme='dark'
  title='Array-Declared Control Panel'
  position='bottom-right'
  settings={[
    { type: 'range', label: 'my range', min: 0, max: 100, initial: 20 },
    { type: 'range', label: 'log range', min: 0.1, max: 100, initial: 20, scale: 'log' },
    { type: 'text', label: 'my text', initial: 'my cool setting' },
    { type: 'checkbox', label: 'my checkbox', initial: true },
    { type: 'color', label: 'my color', format: 'rgb', initial: 'rgb(10,200,0)' },
    {
      type: 'button',
      label: 'gimme an alert',
      action: () => {
        alert('hello!');
      },
      onmousedown: () => console.log('button pressed'),
      onmouseup: () => console.log('button released'),
      disabled: true,
    },
    {
      type: 'select',
      label: 'select one',
      options: ['option 1', 'option 2'],
      initial: 'option 1',
    },
    { type: 'multibox', label: 'check many', count: 3, initial: [true, false, true] },
  ]}
/>

usage

The ControlPanel component takes an array of children setting components that all receive part of the state. They each take additional props that can be used to configure their behavior. The ControlPanel itself takes a variety of props itself:

Each child setting component must be one of RangeInputCheckboxColorIntervalSelect. Each label must be unique as it maps to a top-level key of the state object for the whole ControlPanel.

Some setting components have additional properties:

external state

It is sometimes desirable to hoist the state for a panel up into something like Redux rather than letting the component handle it itself. This is supported by passing a state prop into the ControlPanel which you are in charge of keeping updated with new values provided by the panel's onChange prop callback. Here's an example of how this would work with state hoisted into a parent component:

class Wrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      label1: 'val',
      ...
    };
  }

  render() {
    return (
      <ControlPanel
        state={this.state}
        {/* \/ not necessary - this is inferred automatically when `state` is provided */}
        {/*initialState={this.state}*/}
        onChange={(key, val) => this.setState({ [key]: val })}
        title='Panel with External State'
      />
        ...
    </ControlPanel>
    );
  }
}

Please note that panel context will not work if you use external state.

panel context

As mentioned above, it's possible to pass a callback to the ControlPanel component which will be supplied with a special context object after the component has mounted. This allows the underlying state to be directly viewed and manipulated from other parts of the application while still being reflected dynamically in the UI. Here's an example:

const handleContext = ctx => {
  console.log(ctx['label']); // prints the stored value for that setting
  ctx['my range'] = 10; // this sets the value of the 'my range' setting to 10 in the panel
  ctx['multibox'][1] = false; // this doesn't work; you can only set top-level setting values
  ctx['multibox'] = [true, false, true]; // do this instead
  console.log(Object.entries(ctx)); // this works, even with the polyfill
};

<ControlPanel contextCb={handleContext}>...</ControlPanel>;

In browsers that don't support the ES6 Proxy API, a shallow polyfill is used which allows values to be get, set, and listed which should be enough for most applications.

Please note that you cannot use external state if you are supplying a panel context callback.

development

To develop on this library, simply run the following commands:

Then, open localhost:9000 in your web browser. The page being shown is found in /demo, and any changes to it or the library itself will be hot-reloaded.

building the demo for publishing

  1. Change demo.js to index.js in demo/index.html
  2. Run cp demo/index.html dist/index.html
  3. Comment out the externals in webpack.prod.js
  4. Run yarn build demo
  5. The demo application will be built into dist

see also