All files / packages/theme-selector/src theme-mapper.ts

100% Statements 15/15
100% Branches 8/8
100% Functions 5/5
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174                                                            14x                       14x               14x                                                       14x                                                                     259x             259x   259x 2x     257x 81x     176x             259x             259x                               259x 259x                        
// SPDX-License-Identifier: MIT
/**
 * Maps core theme flavors to UI-specific format
 */
 
import type { ThemeFlavor as CanonicalThemeFlavor } from '@lgtm-hq/turbo-themes-core';
import type { ThemeFamily } from './types.js';
 
export interface ThemeColors {
  bg: string;
  surface: string;
  accent: string;
  text: string;
}
 
export interface ThemeFlavor extends Pick<CanonicalThemeFlavor, 'id' | 'appearance' | 'vendor'> {
  id: string;
  name: string;
  description: string;
  cssFile: string;
  icon?: string | undefined;
  family: ThemeFamily;
  colors: ThemeColors;
}
 
// ============================================================================
// Lookup Tables
// ============================================================================
 
/** Vendor to family mapping */
const VENDOR_FAMILY_MAP: Record<string, ThemeFamily> = {
  bulma: 'bulma',
  catppuccin: 'catppuccin',
  dracula: 'dracula',
  gruvbox: 'gruvbox',
  github: 'github',
  nord: 'nord',
  'rose-pine': 'rose-pine',
  solarized: 'solarized',
  'tokyo-night': 'tokyo-night',
};
 
const DEFAULT_FAMILY: ThemeFamily = 'bulma';
 
/** Icon configuration - string for single icon, object for appearance-specific */
interface AppearanceIcons {
  light: string;
  dark: string;
}
 
const VENDOR_ICON_MAP: Record<string, string | AppearanceIcons> = {
  bulma: 'assets/img/turbo-themes-logo.png',
  catppuccin: {
    light: 'assets/img/catppuccin-logo-latte.png',
    dark: 'assets/img/catppuccin-logo-macchiato.png',
  },
  dracula: 'assets/img/dracula-logo.png',
  gruvbox: {
    light: 'assets/img/gruvbox-light.png',
    dark: 'assets/img/gruvbox-dark.png',
  },
  github: {
    light: 'assets/img/github-logo-light.png',
    dark: 'assets/img/github-logo-dark.png',
  },
  nord: 'assets/img/nord.png',
  'rose-pine': {
    light: 'assets/img/rose-pine-dawn.png',
    dark: 'assets/img/rose-pine.png',
  },
  solarized: {
    light: 'assets/img/solarized-light.png',
    dark: 'assets/img/solarized-dark.png',
  },
  'tokyo-night': 'assets/img/tokyo-night.png',
};
 
/** Predefined flavor descriptions */
const FLAVOR_DESCRIPTIONS: Record<string, string> = {
  'bulma-light': 'Classic Bulma look with a bright, neutral palette.',
  'bulma-dark': 'Dark Bulma theme tuned for low-light reading.',
  'catppuccin-latte': 'Light, soft Catppuccin palette for daytime use.',
  'catppuccin-frappe': 'Balanced dark Catppuccin theme for focused work.',
  'catppuccin-macchiato': 'Deep, atmospheric Catppuccin variant with rich contrast.',
  'catppuccin-mocha': 'Cozy, high-contrast Catppuccin theme for late-night sessions.',
  dracula: 'Iconic Dracula dark theme with vibrant accents.',
  'gruvbox-dark-hard': 'Highest contrast dark Gruvbox palette with deep shadows.',
  'gruvbox-dark': 'Classic Gruvbox dark palette with warm, muted tones.',
  'gruvbox-dark-soft': 'Softer dark Gruvbox palette with reduced contrast.',
  'gruvbox-light-hard': 'Bright, crisp Gruvbox light palette with extra contrast.',
  'gruvbox-light': 'Classic Gruvbox light palette with warm paper tones.',
  'gruvbox-light-soft': 'Soft, low-contrast Gruvbox light palette for long sessions.',
  'github-light': 'GitHub-inspired light theme suited for documentation and UI heavy pages.',
  'github-dark': 'GitHub dark theme optimized for code-heavy views.',
  nord: 'Arctic, north-bluish color palette inspired by the polar night.',
  'rose-pine': 'Elegant dark theme with natural pine and soho vibes.',
  'rose-pine-moon': 'Deeper variant of Rosé Pine with enhanced contrast.',
  'rose-pine-dawn': 'Light Rosé Pine variant for daytime use.',
  'solarized-dark': 'Solarized Dark with a balanced, low-contrast palette.',
  'solarized-light': 'Solarized Light tuned for bright, daylight-friendly UIs.',
  'tokyo-night-dark': 'Deep midnight blues with neon accents.',
  'tokyo-night-storm': 'Stormy variant with richer contrast and depth.',
  'tokyo-night-light': 'Clean daylight palette inspired by Tokyo mornings.',
};
 
// ============================================================================
// Helper Functions
// ============================================================================
 
/**
 * Gets the theme family from vendor name
 */
function getFamily(vendor: string): ThemeFamily {
  return VENDOR_FAMILY_MAP[vendor] ?? DEFAULT_FAMILY;
}
 
/**
 * Gets icon path for a vendor
 */
function getIconForVendor(vendor: string, appearance: 'light' | 'dark'): string | undefined {
  const iconConfig = VENDOR_ICON_MAP[vendor];
 
  if (!iconConfig) {
    return undefined;
  }
 
  if (typeof iconConfig === 'string') {
    return iconConfig;
  }
 
  return iconConfig[appearance];
}
 
/**
 * Gets description for a flavor
 */
function getDescriptionForFlavor(id: string, label: string): string {
  return FLAVOR_DESCRIPTIONS[id] ?? `${label} theme`;
}
 
/**
 * Extracts preview colors from theme tokens
 */
function extractPreviewColors(tokens: CanonicalThemeFlavor['tokens']): ThemeColors {
  return {
    bg: tokens.background.base,
    surface: tokens.background.surface,
    accent: tokens.brand.primary,
    text: tokens.text.primary,
  };
}
 
// ============================================================================
// Public API
// ============================================================================
 
/**
 * Maps a canonical theme flavor to UI-specific format
 */
export function mapFlavorToUI(flavor: CanonicalThemeFlavor): ThemeFlavor {
  const family = getFamily(flavor.vendor);
  return {
    id: flavor.id,
    name: flavor.label,
    description: getDescriptionForFlavor(flavor.id, flavor.label),
    cssFile: `packages/css/dist/themes/${flavor.id}.css`,
    icon: getIconForVendor(flavor.vendor, flavor.appearance),
    family,
    vendor: flavor.vendor,
    appearance: flavor.appearance,
    colors: extractPreviewColors(flavor.tokens),
  };
}