dis-gui-lifetoys
Version:
An extensible, styleable, & React-based controller library inspired by the venerable dat-gui.
349 lines (283 loc) • 8.61 kB
Markdown
# dis-gui-lifetoys
> This repo was forked from [wwwtyro/dis-gui](https://github.com/wwwtyro/dis-gui) and includes the following notable changes:
> - Babel 6 was updated to Babel 7 configured with `preset-env` and `preset-react`
> - React 16 was updated to React 18
> - Added `install-peers-cli` package and `npm run prepare` script to install peer dependencies
> - Added `styled-components` package
> - Range slider now uses a styled `input[type=range]` instead of a custom control
>
> Additional Notes:
> - The components are still using `componentWillReceiveProps()` to update state synchronously such that a re-render is not triggered. Each component has specific UX concerns that require investigation and dev testing. In some cased, `componentDidUpdate()` seems like an appropriate replacement. In other cases, another method may be more suitable.
An extensible, styleable, & React-based controller library inspired by the
venerable [dat-gui](https://github.com/dataarts/dat.gui).
<p align="center">
<img src='media/example-01.png'/>
</p>
The above was created with the following JSX:
```js
import * as dg from 'dis-gui-lifetoys';
...
<dg.GUI>
<dg.Text label='Text' value='Hello world!'/>
<dg.Number label='Number' value={65536}/>
<dg.Number label='Range' value={512} min={-1024} max={1024} step={64}/>
<dg.Checkbox label='Checkbox' checked={true}/>
<dg.Select label='Select' options={['Option one', 'Option two', 'Option three']}/>
<dg.Button label='Button'/>
<dg.Folder label='Folder' expanded={true}>
<dg.Text label='Text' value='Hello folder!'/>
<dg.Number label='Number' value={2}/>
<dg.Folder label='Subfolder' expanded={true}>
<dg.Text label='Text' value='Hello subfolder!'/>
<dg.Number label='Number' value={2}/>
</dg.Folder>
</dg.Folder>
<dg.Color label='Color' expanded={true} red={0} green={128} blue={255}/>
<dg.Gradient label='Gradient' expanded={true}/>
</dg.GUI>
```
## Demo
Try out the [live demo](https://dietrich-stein.github.io/dis-gui-lifetoys).
## Installation
`npm install dis-gui-lifetoys`
> Note: dis-gui-lifetoys has peer dependencies react@^18.0.0 react-dom@^18.0.0
## Events
The **onChange** event is fired when a control's value changes:
```js
<dg.GUI>
<dg.Text
label='Text' value='Hello world!'
onChange={function(value) {console.log(value)}}
/>
</dg.GUI>
```
The **onFinishChange** event fires when the user performs some action that
indicates that they are finished changing a value, like hitting enter or tabbing
out of a text or number field, or releasing a mouse button after dragging a
number range thumb:
```js
<dg.GUI>
<dg.Number
label='Horses'
value={2}
min={0}
max={4}
step={0.1}
onFinishChange={function(value) {console.log(value)}}
/>
</dg.GUI>
```
The **Button** control fires an **onClick** event:
```js
<dg.GUI>
<dg.Button
label='Run The Horses'
onClick={function() {console.log('The horses are running.')}}
/>
</dg.GUI>
```
## Numbers
If you provide a **min** and **max** prop to the **Number** control, you'll get
a range slider and a number field:
```js
<dg.GUI>
<dg.Number
label='Horses'
value={2}
min={0}
max={4}
step={0.1}
onFinishChange={function(value) {console.log(value)}}
/>
</dg.GUI>
```
<p align="center">
<img src='media/example-02.png'/>
</p>
If you don't, it won't:
```js
<dg.GUI>
<dg.Number
label='Horses'
value={2}
onFinishChange={function(value) {console.log(value)}}
/>
</dg.GUI>
```
<p align="center">
<img src='media/example-03.png'/>
</p>
If the user enters a value that is not a number, the **onChange** and
**onFinishChange** event will not fire, and the value will be highlighted
with the value defined by **lowlighterr** in the **style** property of the
**GUI** component:
<p align="center">
<img src='media/number-error.png'/>
</p>
## Folders
Nest controls to arbitrary depth with the **Folder** component:
```js
<dg.GUI>
<dg.Folder label='Folder 1'>
<dg.Folder label='Folder 2'>
<dg.Folder label='Folder 3'>
<dg.Folder label='Folder 4'>
<dg.Folder label='Folder 5'>
<dg.Text label='You' value='...made it!'></dg.Text>
</dg.Folder>
</dg.Folder>
</dg.Folder>
</dg.Folder>
</dg.Folder>
</dg.GUI>
```
<p align="center">
<img src='media/folder-collapsed-expanded.png'/>
</p>
Pass the **expanded** prop a boolean to indicate if the folder
should start out open or closed:
```js
<dg.GUI>
<dg.Folder label='Folder 1' expanded={true}>
<dg.Text label='You' value='...made it!'></dg.Text>
</dg.Folder>
</dg.GUI>
```
<p align="center">
<img src='media/expanded-folder.png'/>
</p>
## Styling
Pass a **style** property to the **GUI** component to change its
appearance:
```js
<dg.GUI style={{
paddingX: 3,
paddingY: 3,
backgroundColor: '#EEE',
lowlight: '#DDD',
lowlighterr: '#FBB',
highlight: '#444',
separator: '1px solid #DDD',
label: {
fontColor: '#444',
fontWeight: 'normal'
}
}}>
<dg.Text label='Text' value='Hello world!'/>
<dg.Number label='Number' value={65536}/>
<dg.Number label='Range' value={512} min={-1024} max={1024} step={64}/>
<dg.Checkbox label='Checkbox' checked={true}/>
<dg.Select label='Select' options={['Option one', 'Option two', 'Option three']}/>
<dg.Button label='Button'/>
<dg.Folder label='Folder' expanded={true}>
<dg.Text label='Text' value='Hello folder!'/>
<dg.Number label='Number' value={2}/>
<dg.Folder label='Subfolder' expanded={true}>
<dg.Text label='Text' value='Hello subfolder!'/>
<dg.Number label='Number' value={2}/>
</dg.Folder>
</dg.Folder>
<dg.Color label='Color' expanded={true} red={0} green={128} blue={255}/>
<dg.Gradient label='Gradient' expanded={true}/>
</dg.GUI>
```
<p align="center">
<img src='media/light-theme.png'/>
</p>
You can change the width of the labels and controls:
```js
<dg.GUI style={{ labelWidth: 100, controlWidth: 400 }}>
<dg.Gradient label='Gradient' expanded={true}/>
</dg.GUI>
```
<p align="center">
<img src='media/wide-gui.png'/>
</p>
And you can position the whole thing:
```js
<dg.GUI style={{top: '0px', right: '0px'}}>
<dg.Gradient label='Gradient' expanded={true}/>
</dg.GUI>
<dg.GUI style={{top: '0px', left: '0px'}}>
<dg.Color label='Color' red={255} green={128} blue={64} expanded={true}/>
</dg.GUI>
<dg.GUI style={{bottom: '0px', right: '0px'}}>
<dg.Text label='Text' value='So many positions!'/>
</dg.GUI>
<dg.GUI style={{bottom: '0px', left: '0px'}}>
<dg.Number label='Number'/>
</dg.GUI>
```
<p align="center">
<img src='media/positions.png'/>
</p>
## Colors
**Color** controls take **red**, **green**, and **blue** props as numbers from
zero to 255:
```js
<dg.GUI>
<dg.Color label='Some blue color' red={64} green={128} blue={255}/>
</dg.GUI>
```
<p align="center">
<img src='media/color-collapsed.png'/>
</p>
...and return an object like the following in their **onChange** and **onFinishChange**
events:
```js
{
red: 64,
green: 128,
blue: 255
}
```
**Color** controls can be expanded by clicking on them, or you can pass the
**expanded** prop a boolean to expand them by default:
```js
<dg.GUI>
<dg.Color label='Some blue color' red={64} green={128} blue={255} expanded={true}/>
</dg.GUI>
```
<p align="center">
<img src='media/color-expanded.png'/>
</p>
## Gradients
The **Gradient** control takes a **stops** prop, which is an array of objects
that have **red**, **green**, **blue**, and **stop** properties. The color
properties behave identically to the **Color** control, and the **stop** property
is a number from zero to one that represents the position of the stop.
```js
<dg.GUI>
<dg.Gradient
label='The floor is lava!'
stops={[
{red: 255, green: 0, blue: 0, stop: 0},
{red: 255, green: 255, blue: 0, stop: 0.5},
{red: 255, green: 255, blue: 255, stop: 1.0},
]}
/>
</dg.GUI>
```
<p align="center">
<img src='media/gradient-collapsed.png'/>
</p>
Like the **Color** control, the **Gradient** control will expand when the user
clicks it or if you add the **expanded** boolean property set to **true**:
```js
<dg.GUI>
<dg.Gradient
label='The floor is lava!'
stops={[
{red: 255, green: 0, blue: 0, stop: 0},
{red: 255, green: 255, blue: 0, stop: 0.5},
{red: 255, green: 255, blue: 255, stop: 1.0},
]}
expanded={true}
/>
</dg.GUI>
```
<p align="center">
<img src='media/gradient-expanded.png'/>
</p>
## Roadmap
* Add documentation about extending `dis-gui-lifetoys`.