UNPKG

@yaireo/knobs

Version:

UI knobs controllers for JS/CSS live manipulation of various parameters

463 lines (376 loc) 14.8 kB
<p align="center"> <br> <a href='https://codepen.io/vsync/pen/KKMwyRO'> <img src="./demo.apng?sanitize=true" alt="Knobs"/> </a> <br> <p> <p align="center"> <a href='https://www.npmjs.com/package/@yaireo/knobs'> <img src="https://img.shields.io/npm/v/@yaireo/knobs.svg" /> </a> <a href='https://simple.wikipedia.org/wiki/MIT_License'> <img src="https://img.shields.io/badge/license-MIT-lightgrey" /> </a> </p> <h1 align="center"> <a href='https://codepen.io/vsync/pen/KKMwyRO'>Knobs</a> 🎛️ UI controllers for JS/CSS manipulation </h1> <h3 align="center"> 👉 <a href='https://codepen.io/vsync/pen/KKMwyRO' target='_blank'>Demo</a> 👈 <br><br> </h3> <table> <tr> <td > <table> <tr> <td>Minified</td> <td align="right"><strong>54kb</strong></td> </tr> <tr> <td>Brotli</td> <td align="right"><strong>13.5kb</strong></td> </tr> <tr> <td>GZIP</td> <td align="right"><strong>15kb</strong></td> </tr> </table> </td> <td> <h2>What is this:</h2> <p> Started as something I needed for my many Codepens - A way to provide viewers, and myself, a set of controllers, for manipulating the DOM instantaneously. </p> <p> Imagine certain aspects of a web page, or a specific *component*, which you would like to add the ability to control on-the-fly. Change the visual looks or certain javascript parameters with a move or a slider. </p> <p> CSS-variables (custom properties) are a great match for this script as they compute in real-time. Javascript is of course a benefitor because every knobs can be attached with a callback that recieves the current value, and additional data, as to what that value should be applied on. </p> <p> It's so easy & quick to use Knobs, about 1 minute! </p> <strong>⚠️ Supported only in modern browsers</strong><br> </td> </tr> </table> ### Features: * `range` input (*wheel*-supported) * `color` input with awesome custom [color picker](https://github.com/yairEO/color-picker) * `checkbox` input * `radio` inputs group * `select` dropdown (native) * Custom HTML-Knobs (add your own buttons or whatever) * Resset all knobs (to defaults) * Reset individual knob * Labels - group all the knobb defined after a certain label * **Expand/Collapse** knobs groups * Apply changes live, on-the-fly, or with an <kbd>Apply</kbd> button * Auto-detect CSS variables defined in knobs as their initialvalues, if possible * Knobs are completely **isolated** within an *iframe* (unaffected by your page styles) * Allows 3 states of visibility: - `0` - Starts as hidden - `1` - Starts as visible - `2` - Always visible * Knobs component placement `position` (relative to window viewport): * `top right` (default) * `bottom right` * `top left` * `bottom left` * Allows theme customization (*currently very limited*) ## Configuration: All is needed is to include the knobs script on the page, and set it up. ```js new Knobs(settings) ``` ### Settings | Name | Type | Default | Info |--------------|-----------------------|---------|-------------------------------------------------------------------------------------------------------------------- | theme | `Object` | | Knobs theme variables. Since the Knobs are encapsulated within an iframe, they cannot be be styled from outside. | live | `Boolean` | `true` | Should changes be immediately applied | persist | `Boolean` | `false` | Persist changes using the browser's localstorage. Store `key/value` per knob, where `key` is the knob's *label*. | visible | `Number` | `0` | `0` - Starts as hidden<br> `1` - Starts as visible<br> `2` - Always visible | CSSVarTarget | `Element`/`NodeList ` | | Global HTML element(s) for which to apply the CSS custom properties.<br> Can also be configured per-knob. | knobsToggle | `Boolean` | `false` | if `true` - adds a checkbox next to each knob, which allows temporarily disabling the knob, reverting to default | knobs | `Array` | | Array of Objects describing the knobs controllers on-screen | standalone | `Boolean` | `false` | if `true` - does not create an iframe and appends it to the page, but simply gives the developer the DOM node, as is, to inject manually with `knobs.DOM.scope` node. Note that CSS in also needed ('./src/styles/styles.scss`) <details> <summary><strong>theme</strong> (defaults)</summary> ```js { styles : ``, // optioanlly add any CSS and it will be injected into the iframe flow : 'horizontal', // use 'compact' to keep things tight position : 'top right', primaryColor: '#0366D6', // mainly for links / range sliders 'base-color': "rgba(0,0,0,1)", // mainly for the background color but also for input fields such as text or number textColor : "#CCC", border : 'none' } ``` </details> <details> <summary><strong>knobs</strong></summary> An array of Objects, where the properties describe a *knob*. It is ***possible*** to define/update the `knobs` Array **after** instance initialization, like so: ```js var myKnobs = new Knobs({ CSSVarTarget:document.body }) // only if working with CSS variables myKnobs.knobs = [{...}, ...] // Add/change the knobs. will automatically re-render (see example further below) ``` All defined *knob* properties, beside a special few, are attributes that are applied on the HTML *input* element that controls the knob, so it is up to the developer who set up the knobs to use the appropriate attributes, for each type of of the supported knobs (`range`, `color`, `checkbox`). The special other properties are: **`onChange`** Callback which fires on every `input` event **`cssVar`** Optional. An array of 3 items: 1. (`String`) - CSS variable name 2. (`String`) - Units (*optional* - Ex. `%` or `px`) 3. (`HTML NODE`) - Reference to an HTML node to apply the knob's CSS variable on (*optional*) Sometimes it is wanted for variables to be defined unitless, for calculation-purposes, like so: ```css div{ --size: 10; /* limits with width to a minimum of 10px by using unitless variable for the "max" function */ width: calc(Max(50, var(--size)) * 1px); } ``` So, when a unitless-variable is desired, but ultimatly it will have a unit, then `units` (*2nd* item in the array) should be written with a dash prefix, Ex.: `-px`, and it will be displayed in the label correctly but ignored when applying the variable. **`cssVarsHSLA`** (boolean) Applies only to *color* knobs and if set to `true` will generate 4 CSS variables for the HSLA version of the color. `--main-color-h`, `--main-color-s`, `--main-color-l` & `--main-color-a`. ```js { cssVar: ['main-color'], cssVarsHSLA: true, label: 'Page background', type: 'color', defaultFormat: 'hsla', }, ``` **`defaultFormat`** (string) Applies only to *color* knobs. Sets the default format displayed to the user and also the value which will be set to the input. Possible values are: `hsla`, `rgba`, `hex`. **`label`** (string) A text which is displayed alongside the knob **`labelTitle`** (string) Optional `title` attribute for the knob's label **`value`** (string, number) Acts as the initial value of the *knob*, except for `checkbox` *knobs*, in which case, if the knob also has `cssVar` property set, then the checkbox is *checked*, that CSS variable `value` will be the `value` property of the knob, Ex. ```js { cssVar: ['hide'], // CSS variable name "--hide" label: 'Show', type: 'checkbox', // checked: true, // not checked by default value: 'none', // if checked: --hide: none; } ``` Then in your CSS you can write the below, so when `--hide` is not defined, `block` is used as the `display` property value. ```css display: var(--hide, block); ``` It is possible to use an *already-declared* CSS-varaible (on the target element) by emmiting the `value` prop from the *knob* decleration. The program will try to get the value using `getComputedStyle` and `getPropertyValue`. Variables which has `calc` or any other computations might result in `NaN`. In which case, a `console.warn` will be presented and a manually typed `value` property for the *knob* would be advised. **`isToggled`** (boolean) If this property is set to `false`, the knob will be toggled *off* by default. Will only take affect if `knobsToggle` setting is set to `true` **`options`** (array) Used for knobs of type `select`. An Array of options to render. [20, 150, [200, '200 nice pixels'], 500] An option can be split to the actual value it represents and its textual value, as the above example shows. **`knobClass`** (string) Add your own *class* to the knob (row) element itself (for styling purposes). Remember that in order to add custom styles, the `theme.styles` setting should be used, because all knobs are encapsulated within an *iframe* so your page styles won't affect anything that's inside. **`render`** (string) Allows to render anything you want in the knob area. Should return a *string* of HTML, for example: ```js { render: ` <button onclick='alert(1)'>1</button> <button onclick='alert(2)'>2</button> `, knobClass: 'custom-actions' } ``` **`script`** (function) A function to be called which has logic related to the custom HTML in the `render` property (shown above). The function recieves 2 arguments: The knobs instance referece and the (auto)generated knob `name` string. ```js { label: 'Custom HTML with label', render: ` <button type='button' class='specialBtn1'>1</button> <button type='button' class='specialBtn2'>2</button> `, script(knobs, name){ knobs.getKnobElm(name).addEventListener("click", e => { if( e.target.tagName == 'BUTTON' ) alert(e.target.textContent) }) }, }, ``` </details> ## Install: ``` npm i @yaireo/knobs ``` **CDN source:** [https://unpkg.com/@yaireo/knobs@latest](https://unpkg.com/@yaireo/knobs@latest) ## Example: ### When Using with NPM, first import `Knobs` ```js import Knobs from '@yaireo/knobs' ``` #### Color manipulation methods: `format` & `CSStoHSLA` are defined on Knobs' instances in `color` property, for example: ```js const myKnobs = new Knobs({ ..., knobs: [ { cssVar: ['bg'], // alias for the CSS variable label: 'Color', type: 'color', value: '#45FDA9', onChange(e, knobData, hsla) => { console.log( myKnobs.format(knobData.value, 'rgb') ) // will print a color string in RGBA } } ] }) myKnobs.color.format() ``` See [color-picker docs](https://github.com/yairEO/color-picker#helper-methods-exported-alongside-the-default-colorpicker) ### Defining Knobs: ```js var settings = { theme: { position: 'bottom right', // default is 'top left' }, // should update immediately (default true) live: false, // 0 - starts as hidden, 1 - starts as visible, 2 - always visible visible: 0, CSSVarTarget: document.querySelector('.testSubject'), knobs: [ { cssVar: ['width', '-px'], // prefix unit with '-' makes it only a part of the title but not of the variable label: 'Width', labelTitle: 'Changes the width at steps of 50 pixels', type: 'range', value: 200, min: 0, max: 500, step: 50, onChange: console.log // javascript callback on every "input" event }, { cssVar: ['width', '-px'], label: 'Width preset', type: 'select', options: [20, 150, [200, '200 nice pixels'], 500], value: 150, // should be one of the options defaultValue: 150 // value for which to reset to (optional) isToggled: false, // this knob will not take affect by default }, { cssVar: ['height', 'vh'], label: 'Height', type: 'range', // value: 20, // if a value is not defined, Knobs will try to get it from the CSS ("CSSVarTarget" selector) automatically min: 0, max: 100, onChange: console.log }, { cssVar: ['align'], label: 'Align boxes', type: 'radio', name: 'align-radio-group', options: [ { value:'left', hidden:true, label: '<svg ...' }, { value: 'center', label:'Center' }, { value:'right', hidden:true, label:'<svg ...' } ], value: 'center', defaultValue: 'left' }, { cssVar: ['radius', '%'], label: 'Radius of the big square here', type: 'range', value: 0, min: 0, max: 50, onChange: console.log }, "Label example", { cssVar: ['bg'], // alias for the CSS variable label: 'Color', type: 'color', defaultFormat: 'hsla', cssVarsHSLA: true, value: '#45FDA9', swatches: ['red', 'gold'], // swatches which can be selected inside the color picker onChange: (e, knobData, hsla) => console.log(e, knobData, hsla, knobData.value) }, { cssVar: ['main-bg', null, document.body], // [alias for the CSS variable, units, applies on element] label: 'Background', type: 'color', value: '#FFFFFF', onChange: (e, knobData, hsla) => console.log(e, knobData, hsla, knobData.value) }, ["Label example", false] // group is collapsed by default { cssVar: ['hide'], // alias for the CSS variable label: 'Show', type: 'checkbox', // checked: true, // default value: 'none', onChange: console.log }, { label: 'Custom with label', render: ` <button type='button' class='specialBtn1'>1</button> <button type='button' class='specialBtn2'>2</button> `, script(knobs, name){ knobs.getKnobElm(name).addEventListener("click", e => { if( e.target.tagName == 'BUTTON' ) alert(e.target.textContent) }) }, }, { render: ` <button type='button' class='specialBtn3'>😎</button> `, script(knobs){ const elm = knobs.DOM.scope.querySelector('.specialBtn3') elm.addEventListener("click", () => alert('😎')) }, knobClass: 'custom-actions' } ] } var penKnobs = new Knobs(settings) ```