Add theme registry for custom protomaps flavor support

Introduces src/themes/registry.js with:
- getTheme(id) - lookup theme config by ID
- getThemeColors(id) - get flavor object (namedTheme for built-ins, custom colors for others)
- getThemeSprite(id) - get sprite URL with fallback for custom themes
- themeList() - list available themes for UI

Updates MapView.jsx:
- Import registry functions instead of namedTheme directly
- buildStyle() uses getThemeColors() and getThemeSprite()
- Overlay add functions use isCurrentThemeDark() helper that checks
  registry dark flag instead of string comparison

Reference files:
- dark-flavor-reference.json - full namedTheme('dark') output (73 flat keys + pois + landcover)
- light-flavor-reference.json - full namedTheme('light') output
- README.md - schema documentation for creating custom themes

This is a refactor only - light/dark themes render identically to before.
Custom themes can now be added to registry.js with full flavor objects.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-05-01 15:27:01 +00:00
commit c701463283
5 changed files with 486 additions and 11 deletions

107
src/themes/registry.js Normal file
View file

@ -0,0 +1,107 @@
/**
* Theme Registry for Navi
*
* Provides a centralized registry for map themes, supporting both built-in
* protomaps themes (light/dark) and custom themes with full flavor objects.
*
* Theme config structure:
* id: string - unique identifier (used in store, data-theme attr)
* name: string - display name for UI
* dark: boolean - true if dark theme (affects overlay styling, sprite fallback)
* colors: object|null - null for built-in themes, full flavor object for custom
* satellite: object|null - raster adjustments when satellite layer is present
* overlay: object|null - reserved for future overlay-specific customizations
*/
import { namedTheme } from 'protomaps-themes-base'
/**
* Theme registry - maps theme IDs to theme configurations
*
* Built-in themes (light/dark) use colors: null to signal that namedTheme()
* should be called at render time. Custom themes provide a full flavor object.
*/
const themes = {
light: {
id: 'light',
name: 'Light',
dark: false,
colors: null, // Use namedTheme('light')
satellite: null,
overlay: null,
},
dark: {
id: 'dark',
name: 'Dark',
dark: true,
colors: null, // Use namedTheme('dark')
satellite: null,
overlay: null,
},
// Custom themes go here. Example:
// 'midnight': {
// id: 'midnight',
// name: 'Midnight',
// dark: true,
// colors: { /* full flavor object matching dark-flavor-reference.json schema */ },
// satellite: { opacity: 0.8, brightnessMin: 0.1 },
// overlay: null,
// },
}
/**
* Get a theme configuration by ID
* @param {string} id - Theme ID
* @returns {object} Theme config, falls back to 'dark' if not found
*/
export function getTheme(id) {
return themes[id] || themes.dark
}
/**
* Get the color flavor for a theme
* For built-in themes, calls namedTheme(). For custom themes, returns colors directly.
* @param {string} id - Theme ID
* @returns {object} Flavor object for use with protomaps layers()
*/
export function getThemeColors(id) {
const theme = getTheme(id)
if (theme.colors === null) {
// Built-in theme - use namedTheme from protomaps-themes-base
return namedTheme(id)
}
return theme.colors
}
/**
* Get the sprite URL for a theme
* Built-in themes use their own sprites. Custom themes fall back to
* dark or light sprite based on the theme's dark flag.
* @param {string} id - Theme ID
* @returns {string} Full sprite URL
*/
export function getThemeSprite(id) {
const theme = getTheme(id)
// Custom themes don't have matching sprites on CDN - fall back based on dark flag
const spriteTheme = theme.colors === null ? id : (theme.dark ? 'dark' : 'light')
return `https://protomaps.github.io/basemaps-assets/sprites/v4/${spriteTheme}`
}
/**
* Get list of available themes for UI display
* @returns {Array<{id: string, name: string, dark: boolean}>}
*/
export function themeList() {
return Object.values(themes).map(({ id, name, dark }) => ({ id, name, dark }))
}
/**
* Check if a theme ID is valid/registered
* @param {string} id - Theme ID to check
* @returns {boolean}
*/
export function isValidTheme(id) {
return id in themes
}
export default themes