Skip to content

Custom Theming System

The DigiNest Maps SDK features a comprehensive, token-based theming engine. Styling variables are declared as TypeScript interfaces, compiled, and injected as CSS Custom Properties (variables) onto the document's :root node, enabling instant application-wide theme flips.


🎨 Theme Tokens Interface

typescript
export interface SpatialTheme {
  /** Mode name */
  mode: 'light' | 'dark';
  
  /** Core application styling hex declarations */
  colors: {
    primary: string;        // default '#2393d4' (DigiNest Blue)
    primaryReshape: string; // default '#086bb7' (Focused/hovered active blue)
    secondary: string;
    accent: string;
    surface: string;
    background: string;
    text: string;
    border: string;
    alarm: string;          // default '#dc2626'
    warning: string;        // default '#c77a15'
    ok: string;             // default '#3aa655'
    nodata: string;         // default '#6a6c6c'
  };

  /** Vector floor plan layer stylings */
  map: {
    wallFill: string;
    floorFill: string;
    doorLine: string;
    amenityIcon: string;
  };

  /** Floating controls UI panel background cards styles */
  controls: {
    floorSwitcherBg: string;
    toolbarBg: string;
    infoCardBg: string;
  };

  /** Sensor kind standard color fallbacks */
  sensorDefaults: Record<SensorKind, string>;

  /** Heatmap aggregation colors */
  heatmap: {
    palette: string[];
  };

  /** Typography settings */
  font: {
    family: string;
    sizeBase: number; // in pixels
  };

  /** Rounded corners radii */
  radius: {
    sm: number;
    md: number;
    lg: number;
  };

  /** Animation timings */
  motion: {
    duration: number; // in milliseconds
    easing: string;   // CSS transition-timing-function e.g. cubic-bezier
  };
}

export type SensorKind = 'temperature' | 'humidity' | 'co2' | 'voc' | 'pressure' | 'peopleCounter';

⚡ Instant High-Performance Light/Dark Diffing

Swapping themes in mapping libraries often causes a severe "style-flash" because all vector sources and layers are completely destroyed and re-loaded.

DigiNest avoids this by leveraging MapLibre's internal style diffing algorithms. When the theme swaps from light to dark:

  1. Style Diffing Execution: The SDK calls:
    typescript
    map.setStyle(newThemeStyleUrl, { diff: true });
  2. No Layer Unmounting: MapLibre parses the difference between the two vector sheets and updates only changed paint properties (e.g. background fill-color).
  3. Preserved Assets: Existing IMDF sources, data layers, wayfinding vectors, and current camera zoom positions remain completely intact and unaffected.
  4. UI Syncing: CSS Custom Variables update on :root concurrently, causing the HTML floating panels (like the Floor Switcher or info cards) to glide smoothly into their dark-mode variables using standard CSS transitions.

🛠️ The <ThemeBuilder> Dev Tool

To help designers and developers tune custom color schemes, the SDK includes an interactive ThemeBuilder component:

tsx
import { SpatialProvider, SpatialMap, ThemeBuilder } from '@diginestai/core';

export function DevSandbox() {
  return (
    <SpatialProvider>
      <SpatialMap buildings={[]}>
        {/* Adds an interactive floating control pane to tune custom colors */}
        <ThemeBuilder />
      </SpatialMap>
    </SpatialProvider>
  );
}

The ThemeBuilder allows you to edit color values, test light/dark transitions live in the browser, and click "Export Tokens" to download a compiled theme.ts file that can be plugged directly back into your production application configurations.


ThemeProvider (F44)

ThemeProvider applies a SpatialTheme to all child components without wrapping the full SpatialProvider. Use it to scope custom themes to sub-trees — for example, a sidebar with different brand colours from the map canvas.

tsx
import { ThemeProvider, SpatialProvider, SpatialMap } from '@diginestai/core';
import { myDarkTheme } from './themes';

export function App() {
  return (
    <SpatialProvider>
      <ThemeProvider theme={myDarkTheme}>
        <SpatialMap buildings={[building]} />
      </ThemeProvider>
    </SpatialProvider>
  );
}

ThemeProvider only overrides the CSS custom properties within its subtree — it does not re-initialise the Zustand store.


SpaceLabels (F45)

SpaceLabels renders text labels on IMDF units. Two display modes are available via the mode prop.

dot-label mode

Renders a small circle with text centred on each unit's display_point.

tsx
import { SpaceLabels } from '@diginestai/core';

<SpatialMap buildings={[building]}>
  <SpaceLabels mode="dot-label" labelField="name" />
</SpatialMap>

thumbnail-label mode (F45 / F21)

Renders a square thumbnail using the thumbnailField property, with an optional occupant name and photo drawn inside.

tsx
<SpatialMap buildings={[building]}>
  <SpaceLabels
    mode="thumbnail-label"
    thumbnailField="photo_url"
    labelField="occupant_name"
    size={48}
  />
</SpatialMap>
PropTypeDefaultDescription
mode'dot-label' | 'thumbnail-label''dot-label'Label rendering mode
labelFieldstring'name'IMDF unit property to use as text label
thumbnailFieldstringIMDF unit property containing image URL (thumbnail-label mode)
sizenumber32Thumbnail size in pixels
minZoomnumber16Zoom level below which labels are hidden

MapMarker (F46)

MapMarker places a static HTML marker at a geographic coordinate. Supports an anchor position for precise callout positioning.

tsx
import { MapMarker, type AnchorPosition } from '@diginestai/core';

<SpatialMap buildings={[building]}>
  <MapMarker
    lng={-79.3806}
    lat={43.6452}
    anchor="bottom"
  >
    <div className="marker-popup">Main Entrance</div>
  </MapMarker>
</SpatialMap>
PropTypeDefaultDescription
lngnumberrequiredLongitude
latnumberrequiredLatitude
anchorAnchorPosition'center'Which part of the element is placed at the coordinate
childrenReactNodeMarker content

AnchorPosition values: 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'


AnimatedMapMarker (F46)

AnimatedMapMarker extends MapMarker with entrance/exit animation and smooth position interpolation when coordinates change.

tsx
import { AnimatedMapMarker, type MarkerAnimation } from '@diginestai/core';

<SpatialMap buildings={[building]}>
  <AnimatedMapMarker
    lng={position.lng}
    lat={position.lat}
    animation="bounce"
    easing="ease-out"
    duration={400}
    anchor="bottom"
  >
    <img src="/pin.svg" alt="Asset pin" />
  </AnimatedMapMarker>
</SpatialMap>
PropTypeDefaultDescription
animationMarkerAnimation'fade'Entrance/exit animation type
easingEasingName'ease'CSS easing for position transitions
durationnumber300Animation duration in ms

MarkerAnimation values: 'fade' | 'bounce' | 'scale' | 'none'

EasingName values: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'

When lng/lat props change, the marker glides to the new position using CSS transforms at the specified easing and duration — useful for smooth RTLS asset tracking.

Released under commercial licensing.