touchbar-electron-renderer

A React renderer for building declarative touchbar interfaces for Electron in Mac OSX.

Usage no npm install needed!

<script type="module">
  import touchbarElectronRenderer from 'https://cdn.skypack.dev/touchbar-electron-renderer';
</script>

README

touchbar-electron-renderer

A React renderer for building declarative touchbar interfaces for Electron in Mac OSX.

semantic-release

This library is in alpha state, use at your own risk

Getting started

First install dependency

npm i touchbar-electron-renderer --save

Create a simple touchbar interface.

import { app, BrowserWindow, TouchBar as NativeTouchBar } from 'electron';
import React, { Component, Fragment } from 'react';
import { ReactTouchBar, TouchBar } from 'touchbar-electron-renderer';

const getRandomValue = () => {
  const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇', '🍀']
  return values[Math.floor(Math.random() * values.length)]
}

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      reel1: null,
      reel2: null,
      reel3: null,
      result: null,
      resultColor: null,
    }

    this.doSpin = this.doSpin.bind(this);
  }

  updateReels() {
    this.setState({
      reel1: getRandomValue(),
      reel2: getRandomValue(),
      reel3: getRandomValue(),
    });
  }

  finishSpin() {
    const { reel1, reel2, reel3 } = this.state;
    const uniqueValues = new Set([reel1, reel2, reel3]).size;

    if (uniqueValues === 1) {
      // All 3 values are the same
      this.setState({
        result: '💰 Jackpot!',
        resultColor: '#FDFF00',
      });
    } else if (uniqueValues === 2) {
      // 2 values are the same
      this.setState({
        result: '😍 Winner!',
        resultColor: '#FDFF00',
      });
    } else {
      // No values are the same
      this.setState({
        result: '🙁 Spin Again',
        resultColor: null,
      });
    }

    this.spinning = false;
  }

  doSpin() {
    if (this.spinning) {
      return;
    }

    this.spinning = true;
    this.setState({
      result: null,
    });

    let timeout = 10
    const spinLength = 4 * 1000 // 4 seconds
    const startTime = Date.now();

    const spinReels = () => {
      this.updateReels()

      if ((Date.now() - startTime) >= spinLength) {
        this.finishSpin();
      } else {
        // Slow down a bit on each spin
        timeout *= 1.1
        setTimeout(spinReels, timeout)
      }
    }

    spinReels()
  }

  render() {
    const { reel1, reel2, reel3, result, resultColor } = this.state;

    return (
      <Fragment>
        <button backgroundColor="#7851A9" onClick={this.doSpin}>
          🎰 Spin
        </button>
        <spacer large />
        <label>{reel1}</label>
        <spacer small />
        <label>{reel2}</label>
        <spacer small />
        <label>{reel3}</label>
        <spacer large />
        <label color={resultColor}>{result}</label>
      </Fragment>
    );
  }
}

function render() {
  ReactTouchBar.render(<App />, new TouchBar(window, NativeTouchBar));
}

let window;
app.once('ready', () => {
  window = new BrowserWindow({
    frame: false,
    titleBarStyle: 'hiddenInset',
    width: 200,
    height: 200,
    backgroundColor: '#000'
  });

  window.loadURL('about:blank');

  render();
})

API

For further reference about TouchBar classes, check the official documentation.

TouchBar

Create TouchBar layouts for native macOS applications

Only the root element should be a TouchBar. The children of TouchBar should be contained in a Fragment or <> element.

Constructor arguments

  • window: The electron window.

Example

const App = () => (
  <>
    <label>Hello world</label>
    <label>This is a touchbar</label>
  </>
)

ReactTouchBar.render(<App />, new TouchBar(window));

TouchBarButton, <button />

Create a button in the touch bar for native macOS applications

<button onClick={someCallback} icon={someIcon}>This is a button</button>

Props

  • children: String, <button /> only accepts text as children. It'll be mapped to label.
  • onClick: Function, It'll be mapped to click in the native element.
  • icon: NativeImage, Button's Icon.
  • iconPosition: String, Can be left, right or overlay.
  • backgroundColor: String, background color.

Note: Use only Hex colors.

For more information go here.

TouchBarColorPicker, <color-picker />

Create a color picker in the touch bar for native macOS applications

<color-picker onChange={chooseColor} selected="#f2a4af">
  <color>#f2a4af</color>
  <color>#d9b1b1</color>
  <color>#bb95a4</color>
  <color>#b3ad99</color>
  <color>#a3b9b7</color>
  <color>#b1b1b0</color>
</color-picker>

Props

  • selected: String, selected color.
  • children: Only <color /> are valid children of <color-picker.
  • onChange: Function, Called when user chooses color.
    • color: String, The chosen color.

<color />

Only valid as children of <color-picker />

<color>#b1b1b0</color>

Props

  • children: String, Hex color.

For more information go here.

TouchBarGroup, <group />

Create a group in the touch bar for native macOS applications

<group>
  <label>Foo bar</label>
  <button>Hello World</button>
</group>

Props

  • children: TouchBar elements.

For more information go here.

TouchBarLabel, <label />

Create a label in the touch bar for native macOS applications

<label color="#f2a4af">Hello world</label>

Props

  • children: String, <label /> only accepts text as children. It'll be mapped to label.
  • color: String, Hex color for text. It'll be mapped to textColor.

For more information go here.

TouchBarPopover, <popover />

Create a popover in the touch bar for native macOS applications

<popover label="😀">
  <button>A button in a popover</button>
  <label>You can use any valid TouchBar element here.</label>
</popover>

Props

  • label: String, Popover button text.
  • icon: NativeImage, Popover's Icon.
  • children: Touchbar elements.
  • hideCloseButton: Boolean, true to hide the close button of the popover. Default false. It'll be mapped & negated to showCloseButton. The reason for this is because is easier to read <popover hideCloseButton /> than <popover showCloseButton={false} />.

For more information go here.

TouchBarScrubber, <scrubber />

Create a scrubber (a scrollable selector)

<scrubber onHighlight={someCallback}>
  <scrub-item>😀</scrub-item>
  <scrub-item>😁</scrub-item>
  <scrub-item>😎</scrub-item>
</scrubber>

Props

  • children: Only <scrub-item /> elements are valid.
  • onSelect: Function, Called when the user taps an item that was not the last tapped item. It'll be mapped to select.
    • selectedIndex: Integer, The index of the item the user selected.
  • onHighlight: Function, Called when the user taps any item. It'll be mapped to highlight.
    • highlightedIndex Integer, The index of the item the user touched.
  • debounceTime: Integer, used to debounce onSelect and onHighlight, default 250.
  • selectedStyle: String, Represents the style that selected items in the scrubber should have. Possible values:
    • background, Maps to [NSScrubberSelectionStyle roundedBackgroundStyle].
    • outline, Maps to [NSScrubberSelectionStyle outlineOverlayStyle].
    • null, Actually null, not a string, removes all styles.
  • overlayStyle: String, Represents the style that selected items in the scrubber should have. This style is overlayed on top of the scrubber item instead of being placed behind it. Possible values:
    • background, Maps to [NSScrubberSelectionStyle roundedBackgroundStyle].
    • outline, Maps to [NSScrubberSelectionStyle outlineOverlayStyle].
    • null, Actually null, not a string, removes all styles.
  • showArrowButtons: Boolean, represents whether to show the left / right selection arrows in this scrubber.
  • mode: String, represents the mode of this scrubber. Possible values:
    • fixed, Maps to NSScrubberModeFixed.
    • free, Maps to NSScrubberModeFree.
  • continuous: Boolean, represents whether this scrubber is continuous or not.

<scrub-item />

Only valid as children of <scrubber />

<scrub-item icon={someIcon}>Some text</scrub-item>

Props

  • children: String, only text is valid.
  • icon: NativeImage.

For more information go here.

TouchBarSegmentedControl, <segmented-control />

Create a segmented control (a button group) where one button has a selected state

<segmented-control style="rounded" onChange={someCallback}>
  <segment>Choose one button</segment>
  <segment>Or another</segment>
  <segment disabled>This segment is disabled</segment>
</segmented-control>

Props

  • children: Only <segment /> elements are valid.
  • segmentStyle: String, valid styles are:
    • automatic - Default. The appearance of the segmented control is automatically determined based on the type of window in which the control is displayed and the position within the window.
    • rounded - The control is displayed using the rounded style.
    • textured-rounded - The control is displayed using the textured rounded style.
    • round-rect - The control is displayed using the round rect style.
    • textured-square - The control is displayed using the textured square style.
    • capsule - The control is displayed using the capsule style.
    • small-square - The control is displayed using the small square style.
    • separated - The segments in the control are displayed very close to each other but not touching.
  • mode: String, Selection mode, valid modes are:
    • single - Default. One item selected at a time, selecting one deselects the previously selected item.
    • multiple - Multiple items can be selected at a time.
    • buttons - Make the segments act as buttons, each segment can be pressed and released but never marked as active.
  • selected: Integer, The index of the currently selected segment, will update automatically with user interaction. When the mode is multiple it will be the last selected item. It'll be mapped to selectedIndex.
  • onChange: Function, called when user chooses a segment.
    • selectedIndex: Integer, The index of the segment the user selected.
    • isSelected: Boolean, Whether as a result of user selection the segment is selected or not.

Only valid as children of <segmented-control />.

<segment icon={someIcon} disabled>Hello world</segment>

Props

  • children: String, only text is valid.
  • icon: NativeImage.
  • disabled: Boolean, if true then disable the segment. It'll be mapped and negated to enabled.

For more information go here.

TouchBarSlider, <slider />

Create a slider in the touch bar for native macOS applications

<slider value={50} minValue={0} maxValue={100} onChange={someCallback}>Choose value</slider>

Props

  • children: String, only text is valid, it'll be mapped to label.
  • value: Integer, selected value.
  • minValue: Integer, Minimum value.
  • maxValue: Integer, Maximum value.
  • onChange: Function, called when the slider is changed. It'll be mapped to change.
    • newValue: Number, The value that the user selected on the Slider.

TouchBarSpacer, <spacer />

Create a spacer between two items in the touch bar for native macOS applications

<spacer large />

Props

  • small: It'll be mapped to size="small".
  • large: It'll be mapped to size="large".
  • flexible: It'll be mapped to size="flexible".

For more information go here.

Known issues

Scrubber updates

When using onHighlight, it's called even when user is scrolling and didn't really chose a new element.

Hooks

For some reason, hooks are not working properly. More investigation is needed.

Renderer

Lots of improvements are still needed. At this point, the code is more an ugly hack than a real solution.

Next steps

  • Add tests.
  • Write more complex examples.
  • Fix bugs.
  • Improve performance.
  • Add support for esc key.