Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-sunbeam

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-sunbeam

Spatial navigation and focus management for React

  • 1.0.5
  • latest
  • Source
  • npm
  • Socket score

Version published
Maintainers
1
Created
Source
Sunbeam Logo

react-sunbeam

Spatial navigation and focus management system for React apps

Test Status Netlify Status npm version

Installation

npm install react-sunbeam

or

yarn add react-sunbeam

Usage

// app.js
import React, { useCallback, useEffect } from "react"
import { Focusable, SunbeamProvider, FocusManager, useSunbeam } from "react-sunbeam"
import { FocusableCard } from "./FocusableCard"

function App() {
    const { setFocus, moveFocusLeft, moveFocusRight, moveFocusUp, moveFocusDown } = useSunbeam()

    const onKeyDown = useCallback(
        (event) => {
            if (!(event instanceof KeyboardEvent)) return
            switch (event.key) {
                case "ArrowRight":
                    moveFocusRight()
                    return
                case "ArrowLeft":
                    moveFocusLeft()
                    return
                case "ArrowUp":
                    moveFocusUp()
                    return
                case "ArrowDown":
                    moveFocusDown()
                    return
            }
        },
        [focusManager]
    )

    useEffect(() => {
        document.addEventListener("keydown", onKeyDown)
        return () => document.removeEventListener("keydown", onKeyDown)
    }, [onKeyDown])

    return (
        <div>
            <FocusableCard focusKey="card-1" />
            <Focusable focusKey="item1">
                {({ focused }) => <div>{focused ? "I am focused" : "I am not focused"}</div>}
            </Focusable>
            <Focusable focusKey="menuContainer">
                <div>
                    <Focusable focusKey="menuItem1">
                        {({ focused }) => (
                            <div style={{ backgroundColor: focused ? "salmon" : "deepskyblue" }}>
                                You can nest Focusables
                            </div>
                        )}
                    </Focusable>
                    <Focusable focusKey="menuItem2">
                        {({ focused }) => (
                            <div style={{ backgroundColor: focused ? "salmon" : "deepskyblue" }}>
                                In this case Sunbeam will try to find the best candidate for the focus within the common
                                Focusable parent first
                            </div>
                        )}
                    </Focusable>
                </div>
            </Focusable>
            <Focusable focusKey="item2">
                {({ focused, path }) => (
                    <div style={{ textDecoration: focused ? "underline" : "none" }} onClick={() => setFocus(path)}>
                        You can also programmatically change focus by using `setFocus` API
                    </div>
                )}
            </Focusable>
        </div>
    )
}

const focusManager = new FocusManager({
    initialFocusPath: ["menuContainer", "menuItem2"],
})

render(
    <SunbeamProvider focusManager={focusManager}>
        <App />
    </SunbeamProvider>,
    document.getElementById("app")
)

// FocusableCard.js
import React, { useRef } from "react"
import { useFocusable } from "react-sunbeam"

export function FocusableCard({ focusKey }) {
    const elementRef = useRef(null)
    const { focused } = useFocusable({ focusKey, elementRef })

    return (
        <div ref={elementRef} style={{ border: focused ? "1px solid salmon" : "1px solid transparent" }}>
            Card
        </div>
    )
}

API

FocusManager

FocusManager is responsible for maintaining the tree of focusable nodes and updating the currently focused node. Usually, your app will only have a single instance of it which you pass to a <SunbeamProvider> component

moveLeft() / moveRight() / moveUp() / moveDown()

These are the most important methods of FocusManager. They move focus to the nearest focusable node in the corresponding direction. You can call these methods in response to any events you want: key presses, game controller button presses, scroll events, etc.

setFocus(path: string[]): void

Immediately makes focused the focusable node with the given path.

Example
import { render } from "react-dom"
import { SunbeamProvider, FocusManager } from "react-sunbeam"
import { App } from "./App"

const focusManager = new FocusManager({
    initialFocusPath: ["menuContainer", "menuItem1"],
})

// Use arrow key presses to control focus.
document.addEventListener("keydown", (event) => {
    switch (event.key) {
        case "ArrowRight":
            focusManager.moveRight()
            return
        case "ArrowLeft":
            focusManager.moveLeft()
            return
        case "ArrowUp":
            focusManager.moveUp()
            return
        case "ArrowDown":
            focusManager.moveDown()
            return
        case "Backspace":
            focusManager.setFocus(["carousel", "item-1"])
            return
    }
})

render(
    <SunbeamProvider focusManager={focusManager}>
        <App />
    </SunbeamProvider>,
    document.getElementById("app")
)

SunbeamProvider

Focusable

useSunbeam(): { setFocus(focusPath: readonly string[]): void; moveFocusRight(): void; moveFocusLeft(): void; moveFocusUp(): void; moveFocusDown(): void; }

This hook provides access to some public methods of FocusManager inside the React components. It expects <SunbeamProvider> to be present in the tree, otherwise it returns no-op versions of the methods

useFocusable(options: Options): { focused: boolean; path: string[] }

This hook makes the enclosing component focusable. It can only be used for the "leaf" focusables so the component that uses it cannot have other focusable children. If you need the latter behaviour use <Focusable> instead.

Options
type Options = {
    // Ref object pointing to a DOM Element or any other object that has 'getBoundingClientRect(): ClientRect' method
    elementRef: React.RefObject<{ getBoundingClientRect(): ClientRect }>
    // If set to false the node is ignored in focus management process. Default: true
    focusable?: boolean
    focusKey?: string
    // If set prevents the node from losing focus when navigating in the given directions
    lock?: Direction | Direction[]
    onKeyPress?: (event: KeyboardEvent) => void
    onFocus?: (event: { focusablePath: readonly string[]; getBoundingClientRect: () => ClientRect }) => void
    onBlur?: (event: { focusablePath: readonly string[]; getBoundingClientRect: () => ClientRect }) => void
}
Example
import React from "react"
import { useFocusable } from "react-sunbeam"

export function FocusableButton({ children }) {
    const ref = React.useRef<HTMLButtonElement>(null)
    const { focused } = useFocusable({
        elementRef: ref,
        onKeyPress(event) {
            if (event.key === "Enter") {
                event.stopPropagation()
                alert("Click!")
            }
        },
    })

    return (
        <button style={{ border: focused ? "2px solid black" : "2px solid transparent" }} ref={ref}>
            {children}
        </button>
    )
}

KeyPressManager

TODO

FAQs

Package last updated on 15 Nov 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc