Skip to content

WIP-0003: Slots Enhancement Specification

Introduction

Slots are a core mechanism in Whitebox that allows plugins to provide UI components at specific locations within the application. This WIP describes the overall slot architecture and proposes enhancements to make it more flexible.

Slotting

To allow plugins to augment the app, we are using a slotting mechanism. Slots are defined by the core app, as well as by plugins.

For example, on the dashboard, we'll have a slot for the map display. The core will, inside this slot, render the component that a designated plugin with map capability with map.display component definition provides. A plugin may technically provide a map component that is not an actual map, illustrating that the core system should not make any assumptions about the components provided by plugins.

An example of a slot usage, rendering a map component, within a dashboard:

const Dashboard = () => (
  <div>
    <h1>Dashboard</h1>
    <SlotLoader name="map.display" />
  </div>
);

This will render the component that a plugin with the map capability provides for display.

Enhancement Proposal

We propose enhancing the SlotLoader component with two new props:

  1. required (default: false):

    • When true: Displays an error when component not found
    • When false: No error displayed if component not found, renders nothing instead
  2. collection (default: false):

    • When true: Renders all matching components for the slot
    • When false: Renders only the first matching component

Usage

// Required slot (shows error if not found)
<SlotLoader name="map.unknown" required />

// Collection slot (shows all matching components)
<SlotLoader name="map.display" collection />

// Both optional (default) and not a collection (default)
// This will not show an error if the component is not found
// And will only show the first matching component
<SlotLoader name="sidebar.widget" />

Example

Slotting

For example, slot C1 to C5 would be implemented as follows:

const MapArea = () => {
  return (
    <div className="c_map_area w-dvw h-full">
      // C1
      <SlotLoader name="map.display" required />

      // C2
      <div className="absolute top-0 left-0">
        <SlotLoader name="flight.overlay.info" required />
      </div>

      // C3
      <div className="absolute bottom-0 left-0">
        <SlotLoader name="devices.overlay.list" collection />
      </div>

      // C4
      <div className="absolute bottom-0 right-0">
        <SlotLoader name="flight.overlay.notification" />
      </div>

      // C5
      <div className="absolute top-0 right-0">
        <SlotLoader name="flight.overlay.action" required />
      </div>
    </div>
  )
}

Plugin Changes

For exporting multiple components for the same slot, plugins would need to add a suffix to the slot name in the slot_component_map to differentiate themselves. For example:

Plugin A:

slot_component_map = {
    "map.display": "whitebox_plugin_gps_display/Map",
}

Plugin B:

slot_component_map = {
    "map.display.extras_1": "whitebox_plugin_gps_display_extras/MapExtras1",
    "map.display.extras_xyz": "whitebox_plugin_gps_display_extras/MapExtrasXYZ",
}

SlotLoader will search for all components matching pattern map.display.* and render them in a list format. This allows for multiple plugins to provide components for the same slot without conflicts.