Skip to content

matthieuGravy/love-on-the-route

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Love On The Route logo

npm version npm downloads license

Love On The Route

The 3 annoying things solved for vanilla JS/TS SPAs: routing, navigation & SEO

📚 Documentation | 💻 GitHub

🌍 Language / Langue:

🇬🇧 English 🇫🇷 Français


English

The 3 annoying things solved for your vanilla JS/TS SPAs: routing, navigation, SEO, multi-language

What it solves

  • File-based routing - Create pages/About.ts → Route /about automatically
  • Automatic navigation - Generate menus without imposed CSS
  • SEO helpers - Meta tags in one line

Installation

npm install love-on-the-route

Ultra Simple Usage - 4 lines

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();

That's it! Your pages in pages/ become navigable routes.

File structure

src/
├── pages/
│   ├── Home.ts      # → Route "/"
│   ├── About.ts     # → Route "/about"
│   └── Contact.ts   # → Route "/contact"
└── main.ts

Creating a page

// pages/Services.ts
export default function Services(): HTMLElement {
  const container = document.createElement("div");
  container.innerHTML = `
    <h1>Our Services</h1>
    <p>List of our services...</p>
  `;
  return container;
}

Automatically:

  • Route /services created
  • Ready for navigation and SEO (if you add them)

Usage levels

1. Ultra Simple (4 lines) - Just Routing

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();

âś… What you get: File-based routing
❌ What you don't get: Navigation, SEO (you add them if needed)

2. With Navigation (7 lines) - Routing + Nav

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
  LoveNav,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
    title?: string;
    description?: string;
    keywords?: string[];
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
const nav = new LoveNav(routes.map((r) => ({ path: r.path, title: r.title })));
document.body.insertBefore(nav.render(), document.querySelector("#content"));
generateRoutes(router, routes);
router.render();

âś… What you get: File-based routing + Automatic navigation
❌ What you don't get: SEO (you add it if needed)

3. With SEO - Manual SEO Control

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
  updateSEO,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
    title?: string;
    description?: string;
    keywords?: string[];
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);

const routesWithSEO = routes.map((route) => ({
  ...route,
  component: () => {
    const pageModule = Object.values(pages).find(
      (module) => module.default.name === route.component.name
    );

    updateSEO({
      title: pageModule?.title || route.title,
      description: pageModule?.description,
      url: window.location.href,
      keywords: pageModule?.keywords,
    });

    return route.component();
  },
}));

const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);
router.render();

Simpler alternative - Add SEO directly to your pages:

// pages/About.ts
import { updateSEO } from "love-on-the-route";

export const title = "About";
export const description = "Learn more about our company";
export const keywords = ["company", "history", "team"];

export default function About(): HTMLElement {
  updateSEO({
    title,
    description,
    url: window.location.href,
    keywords,
  });

  const container = document.createElement("div");
  container.innerHTML = `
    <h1>${title}</h1>
    <p>Discover our story...</p>
  `;
  return container;
}

âś… What you get: File-based routing + Manual SEO control
âś… What you control: Exactly when and how SEO is updated

Multilingual support

Multilingual structure

pages/
├── en/
│   ├── Home.ts     # → Route "/en"
│   └── About.ts    # → Route "/en/about"
└── fr/
    ├── Home.ts     # → Route "/fr"
    └── About.ts    # → Route "/fr/about"

Automatic detection

const { routes, languages, isMultilingual } =
  autoDiscoverPagesIntelligent(pages);

if (isMultilingual) {
  console.log("Detected languages:", languages); // ["en", "fr"]
  // Automatic language selector configuration
}

Full multilingual usage

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
  updateSEO,
  LoveNav,
  LangSelector,
  filterRoutesByCurrentLanguage,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
    title?: string;
    description?: string;
    keywords?: string[];
  }
>;

const { routes, languages, isMultilingual } =
  autoDiscoverPagesIntelligent(pages);

const routesWithSEO = routes.map((route) => ({
  ...route,
  component: () => {
    const pageModule = Object.values(pages).find(
      (module) => module.default.name === route.component.name
    );

    updateSEO({
      title: pageModule?.title || route.title,
      description: pageModule?.description,
      url: window.location.href,
      keywords: pageModule?.keywords,
    });

    return route.component();
  },
}));

const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);

let nav: LoveNav;
const updateNavigation = () => {
  const currentLanguageRoutes = filterRoutesByCurrentLanguage(
    routes,
    languages
  );
  const navRoutes = currentLanguageRoutes.map((r) => ({
    path: r.path,
    title: r.title,
  }));

  if (nav) {
    nav.updateRoutes(navRoutes);
  } else {
    nav = new LoveNav(navRoutes);
    document.body.insertBefore(
      nav.render(),
      document.querySelector("#content")
    );
  }
};

updateNavigation();

if (isMultilingual && languages) {
  const langOptions = languages.map((lang) => ({
    code: lang,
    label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
    flag: lang === "en" ? "🇬🇧" : lang === "fr" ? "🇫🇷" : undefined,
  }));

  const langSelector = new LangSelector(langOptions, {
    containerClass: "language-selector",
    linkClass: "lang-link",
    activeClass: "active",
    showFlags: true,
  });

  document.body.insertBefore(
    langSelector.render(),
    document.querySelector("#content")
  );

  window.addEventListener("routeChanged", updateNavigation);
}

router.render();

if (isMultilingual && window.location.pathname === "/") {
  const defaultLang = languages?.[0] || "en";
  router.navigate(`/${defaultLang}`);
}

Alternative with SVG/images :

if (isMultilingual && languages) {
  const langOptions = languages.map((lang) => ({
    code: lang,
    label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
    flag:
      lang === "en"
        ? `<img src="/flags/en.svg" alt="EN" width="16" height="12" style="margin-right: 4px;">`
        : lang === "fr"
        ? `<svg width="16" height="12" style="margin-right: 4px;"><rect width="16" height="4" fill="#0055A4"/><rect y="4" width="16" height="4" fill="white"/><rect y="8" width="16" height="4" fill="#EF4135"/></svg>`
        : undefined,
  }));

  const langSelector = new LangSelector(langOptions, {
    containerClass: "language-selector",
    linkClass: "lang-link",
    activeClass: "active",
    showFlags: true,
  });

  document.body.insertBefore(
    langSelector.render(),
    document.querySelector("#content")
  );

  window.addEventListener("routeChanged", updateNavigation);
}

Logo Configuration with LoveNav

New: LoveNav now supports native logo integration with multiple configuration options.

Option 1: Logo integrated in navigation (replaces Home)

import { LoveNav } from "love-on-the-route";

const routes = [
  { path: "/", title: "Home" },
  { path: "/about", title: "About" },
  { path: "/contact", title: "Contact" },
];

const nav = new LoveNav(routes, {
  containerClass: "main-navigation",
  linkClass: "nav-link",
  activeClass: "active",
  logo: {
    html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
    href: "/",
    replacesHome: true, // Logo replaces the "Home" link
    linkClass: "logo-link",
    containerClass: "logo-container",
  },
});

document.body.appendChild(nav.render());

Option 2: Logo separated from navigation

const nav = new LoveNav(routes, {
  containerClass: "main-navigation",
  linkClass: "nav-link",
  activeClass: "active",
  separateLogoFromNav: true, // Logo rendered separately
  logo: {
    html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
    href: "/",
    linkClass: "logo-link",
    containerClass: "logo-container",
  },
});

const { logo, nav: navElement } = nav.renderSeparate();

// Full control over placement
const header = document.createElement("header");
header.style.cssText =
  "display: flex; justify-content: space-between; align-items: center; padding: 1rem;";

if (logo) header.appendChild(logo);
header.appendChild(navElement);
document.body.appendChild(header);

Option 3: Logo coexisting with Home

const nav = new LoveNav(routes, {
  logo: {
    html: `<img src="/logo.svg" alt="My Site" width="80" height="30">`,
    href: "/",
    replacesHome: false, // Logo AND Home link (default)
    linkClass: "logo-link",
  },
});

Language Detection & Dynamic Components

New: Access current language for dynamic component localization.

import {
  getCurrentLanguage,
  detectCurrentLanguage,
  watchLanguageChanges,
} from "love-on-the-route";

// Simple current language detection
const currentLang = getCurrentLanguage();
console.log(currentLang); // 'fr', 'en', etc.

// Language detection with supported languages
const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // With guaranteed fallback

// Watch for language changes (reactive components)
const unsubscribe = watchLanguageChanges((newLang) => {
  console.log("Language changed to:", newLang);
  // Update your component...
});

// Stop watching
unsubscribe();

Example: Localized Component

import { getCurrentLanguage, watchLanguageChanges } from "love-on-the-route";

class LocalizedComponent {
  private currentLang: string;
  private translations = {
    en: { welcome: "Welcome", button: "Click me" },
    fr: { welcome: "Bienvenue", button: "Cliquez-moi" },
    es: { welcome: "Bienvenido", button: "Haz clic" },
  };

  constructor() {
    this.currentLang = getCurrentLanguage();
    this.render();

    // Auto-update on language changes
    watchLanguageChanges((newLang) => {
      this.currentLang = newLang;
      this.render();
    });
  }

  render() {
    const t = this.translations[this.currentLang] || this.translations.en;
    const container = document.createElement("div");
    container.innerHTML = `
      <h1>${t.welcome}</h1>
      <button>${t.button}</button>
    `;
    return container;
  }
}

Multilingual configuration with logo

const { routes, languages, isMultilingual } =
  autoDiscoverPagesIntelligent(pages);

if (isMultilingual) {
  const getHomePath = (): string => {
    const currentPath = window.location.pathname;
    const currentLang = languages.find((lang) =>
      currentPath.startsWith(`/${lang}`)
    );
    return `/${currentLang || languages[0]}`;
  };

  let nav: LoveNav;
  const updateNavigation = () => {
    const currentLanguageRoutes = filterRoutesByCurrentLanguage(
      routes,
      languages
    );
    const navRoutes = currentLanguageRoutes.map((r) => ({
      path: r.path,
      title: r.title,
    }));

    if (nav) {
      nav.updateRoutes(navRoutes);
      // Update logo href for current language
      nav.updateLogo({
        html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
        href: getHomePath(),
        replacesHome: true,
        linkClass: "logo-link",
      });
    } else {
      nav = new LoveNav(navRoutes, {
        logo: {
          html: `<img src="/logo.svg" alt="My Site" width="120" height="40">`,
          href: getHomePath(),
          replacesHome: true,
          linkClass: "logo-link",
        },
      });
      document.body.appendChild(nav.render());
    }
  };

  updateNavigation();
  window.addEventListener("routeChanged", updateNavigation);
}

Logo API

interface LogoConfig {
  html: string; // Logo HTML (img, svg, text...)
  href?: string; // Destination URL (default: "/")
  replacesHome?: boolean; // If true, replaces "Home" links (default: false)
  containerClass?: string; // Container CSS class
  linkClass?: string; // Link CSS class
}

Available methods:

  • nav.render() - Normal rendering (logo integrated if configured)
  • nav.renderSeparate() - Separate rendering { logo?, nav }
  • nav.updateLogo(logoConfig) - Dynamic logo update

Option 4: Manual home filtering (when logo is managed separately)

If you're managing your logo separately from LoveNav, you can manually filter home routes:

const updateNavigation = () => {
  const currentLanguageRoutes = filterRoutesByCurrentLanguage(
    routes,
    languages
  );

  // Manually filter out home routes
  const navRoutes = currentLanguageRoutes
    .filter((r) => {
      const path = r.path.toLowerCase();
      const title = r.title.toLowerCase();

      // Exclude home routes
      const isHomeRoute =
        path === "/" ||
        path.match(/^\/[a-z]{2}$/) || // /en, /fr, etc.
        title === "home" ||
        title === "accueil";

      return !isHomeRoute;
    })
    .map((r) => ({ path: r.path, title: r.title }));

  // Your separate logo management
  logoLink.href = getHomePath();

  if (nav) {
    nav.updateRoutes(navRoutes);
  } else {
    nav = new LoveNav(navRoutes, {
      containerClass: "main-navigation",
      linkClass: "nav-link",
      activeClass: "active",
    });
    navContainer.appendChild(nav.render());
  }
};

This approach is useful when:

  • You have a complex logo setup
  • You want full control over logo positioning
  • You're using a separate navigation container

Language Hooks

Access and react to language changes in your components with these 3 simple hooks:

1. Simple current language detection

import { getCurrentLanguage } from "love-on-the-route";

const lang = getCurrentLanguage();
console.log(lang); // 'fr', 'en', etc.

2. Language detection with supported languages

import { detectCurrentLanguage } from "love-on-the-route";

const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // With guaranteed fallback

3. 🆕 Watch for language changes

import { watchLanguageChanges } from "love-on-the-route";

// Listen to changes
const unsubscribe = watchLanguageChanges((newLang) => {
  console.log("Language changed to:", newLang);
  // Update your component...
});

// Stop listening
unsubscribe();

Why Love On The Route?

  • Ultra simple - 4 lines to start
  • Zero imposed CSS - Your design, your classes
  • File-based routing - Like Next.js but vanilla
  • SEO ready - Automatic meta tags
  • TypeScript - Full support
  • Multilingual - Automatic detection
  • Flexible names - No constraints on file names

Français

Les 3 trucs chiants en moins pour vos SPAs vanilla JS/TS : routing, navigation, SEO, multi langue

Ce que ça résout

  • Routing file-based - CrĂ©ez pages/About.ts → Route /about automatique
  • Navigation automatique - GĂ©nĂ©ration des menus sans CSS imposĂ©
  • SEO helpers - Meta tags en une ligne

Installation

npm install love-on-the-route

Usage Ultra Simple - 4 lignes

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();

C'est tout ! Vos pages dans pages/ deviennent des routes navigables.

Structure des fichiers

src/
├── pages/
│   ├── Home.ts      # → Route "/"
│   ├── About.ts     # → Route "/about"
│   └── Contact.ts   # → Route "/contact"
└── main.ts

Créer une page

// pages/Services.ts
export default function Services(): HTMLElement {
  const container = document.createElement("div");
  container.innerHTML = `
    <h1>Nos Services</h1>
    <p>Liste de nos services...</p>
  `;
  return container;
}

Automatiquement :

  • Route /services créée
  • PrĂŞt pour navigation et SEO (si vous les ajoutez)

Niveaux d'usage

1. Ultra Simple (4 lignes) - Juste le Routing

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
generateRoutes(router, routes);
router.render();

âś… Ce que vous obtenez : Routing file-based
❌ Ce que vous n'obtenez pas : Navigation, SEO (vous les ajoutez si besoin)

2. Avec Navigation (7 lignes) - Routing + Nav

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
  LoveNav,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
    title?: string;
    description?: string;
    keywords?: string[];
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);
const router = createRouter(document.body);
const nav = new LoveNav(routes.map((r) => ({ path: r.path, title: r.title })));
document.body.insertBefore(nav.render(), document.querySelector("#content"));
generateRoutes(router, routes);
router.render();

âś… Ce que vous obtenez : Routing file-based + Navigation automatique
❌ Ce que vous n'obtenez pas : SEO (vous l'ajoutez si besoin)

3. Avec SEO - ContrĂ´le manuel du SEO

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
  updateSEO,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
    title?: string;
    description?: string;
    keywords?: string[];
  }
>;
const { routes } = autoDiscoverPagesIntelligent(pages);

const routesWithSEO = routes.map((route) => ({
  ...route,
  component: () => {
    const pageModule = Object.values(pages).find(
      (module) => module.default.name === route.component.name
    );

    updateSEO({
      title: pageModule?.title || route.title,
      description: pageModule?.description,
      url: window.location.href,
      keywords: pageModule?.keywords,
    });

    return route.component();
  },
}));

const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);
router.render();

Alternative plus simple - Ajouter SEO directement dans vos pages :

// pages/About.ts
import { updateSEO } from "love-on-the-route";

export const title = "Ă€ propos";
export const description = "En savoir plus sur notre entreprise";
export const keywords = ["entreprise", "histoire", "équipe"];

export default function About(): HTMLElement {
  updateSEO({
    title,
    description,
    url: window.location.href,
    keywords,
  });

  const container = document.createElement("div");
  container.innerHTML = `
    <h1>${title}</h1>
    <p>Découvrez notre histoire...</p>
  `;
  return container;
}

âś… Ce que vous obtenez : Routing file-based + Manual SEO control
âś… Ce que vous contrĂ´lez : Exactement quand et comment le SEO est mis Ă  jour

Support multilingue

Structure multilingue

pages/
├── en/
│   ├── Home.ts     # → Route "/en"
│   └── About.ts    # → Route "/en/about"
└── fr/
    ├── Home.ts     # → Route "/fr"
    └── About.ts    # → Route "/fr/about"

Détection automatique

const { routes, languages, isMultilingual } =
  autoDiscoverPagesIntelligent(pages);

if (isMultilingual) {
  console.log("Langues détectées :", languages); // ["en", "fr"]
  // Configuration automatique du sélecteur de langue
}

Usage complet multilingue

import {
  createRouter,
  autoDiscoverPagesIntelligent,
  generateRoutes,
  updateSEO,
  LoveNav,
  LangSelector,
  filterRoutesByCurrentLanguage,
} from "love-on-the-route";

const pages = import.meta.glob("./pages/**/*.ts", { eager: true }) as Record<
  string,
  {
    default: () => HTMLElement;
    title?: string;
    description?: string;
    keywords?: string[];
  }
>;

const { routes, languages, isMultilingual } =
  autoDiscoverPagesIntelligent(pages);

const routesWithSEO = routes.map((route) => ({
  ...route,
  component: () => {
    const pageModule = Object.values(pages).find(
      (module) => module.default.name === route.component.name
    );

    updateSEO({
      title: pageModule?.title || route.title,
      description: pageModule?.description,
      url: window.location.href,
      keywords: pageModule?.keywords,
    });

    return route.component();
  },
}));

const router = createRouter(document.body);
generateRoutes(router, routesWithSEO);

let nav: LoveNav;
const updateNavigation = () => {
  const currentLanguageRoutes = filterRoutesByCurrentLanguage(
    routes,
    languages
  );
  const navRoutes = currentLanguageRoutes.map((r) => ({
    path: r.path,
    title: r.title,
  }));

  if (nav) {
    nav.updateRoutes(navRoutes);
  } else {
    nav = new LoveNav(navRoutes);
    document.body.insertBefore(
      nav.render(),
      document.querySelector("#content")
    );
  }
};

updateNavigation();

if (isMultilingual && languages) {
  const langOptions = languages.map((lang) => ({
    code: lang,
    label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
    flag: lang === "en" ? "🇬🇧" : lang === "fr" ? "🇫🇷" : undefined,
  }));

  const langSelector = new LangSelector(langOptions, {
    containerClass: "language-selector",
    linkClass: "lang-link",
    activeClass: "active",
    showFlags: true,
  });

  document.body.insertBefore(
    langSelector.render(),
    document.querySelector("#content")
  );

  window.addEventListener("routeChanged", updateNavigation);
}

router.render();

if (isMultilingual && window.location.pathname === "/") {
  const defaultLang = languages?.[0] || "en";
  router.navigate(`/${defaultLang}`);
}

Alternative avec SVG/images :

if (isMultilingual && languages) {
  const langOptions = languages.map((lang) => ({
    code: lang,
    label: lang === "en" ? "English" : lang === "fr" ? "Français" : lang,
    flag:
      lang === "en"
        ? `<img src="/flags/en.svg" alt="EN" width="16" height="12" style="margin-right: 4px;">`
        : lang === "fr"
        ? `<svg width="16" height="12" style="margin-right: 4px;"><rect width="16" height="4" fill="#0055A4"/><rect y="4" width="16" height="4" fill="white"/><rect y="8" width="16" height="4" fill="#EF4135"/></svg>`
        : undefined,
  }));

  const langSelector = new LangSelector(langOptions, {
    containerClass: "language-selector",
    linkClass: "lang-link",
    activeClass: "active",
    showFlags: true,
  });

  document.body.insertBefore(
    langSelector.render(),
    document.querySelector("#content")
  );

  window.addEventListener("routeChanged", updateNavigation);
}

Configuration Logo avec LoveNav

Nouveau : LoveNav supporte maintenant l'intégration native du logo avec plusieurs options de configuration.

Option 1: Logo intégré dans la navigation (remplace Home)

import { LoveNav } from "love-on-the-route";

const routes = [
  { path: "/", title: "Home" },
  { path: "/about", title: "About" },
  { path: "/contact", title: "Contact" },
];

const nav = new LoveNav(routes, {
  containerClass: "main-navigation",
  linkClass: "nav-link",
  activeClass: "active",
  logo: {
    html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
    href: "/",
    replacesHome: true, // Le logo remplace le lien "Home"
    linkClass: "logo-link",
    containerClass: "logo-container",
  },
});

document.body.appendChild(nav.render());

Option 2: Logo séparé de la navigation

const nav = new LoveNav(routes, {
  containerClass: "main-navigation",
  linkClass: "nav-link",
  activeClass: "active",
  separateLogoFromNav: true, // Logo rendu séparément
  logo: {
    html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
    href: "/",
    linkClass: "logo-link",
    containerClass: "logo-container",
  },
});

const { logo, nav: navElement } = nav.renderSeparate();

// ContrĂ´le total sur le placement
const header = document.createElement("header");
header.style.cssText =
  "display: flex; justify-content: space-between; align-items: center; padding: 1rem;";

if (logo) header.appendChild(logo);
header.appendChild(navElement);
document.body.appendChild(header);

Option 3: Logo coexistant avec Home

const nav = new LoveNav(routes, {
  logo: {
    html: `<img src="/logo.svg" alt="Mon Site" width="80" height="30">`,
    href: "/",
    replacesHome: false, // Logo ET lien Home (défaut)
    linkClass: "logo-link",
  },
});

Détection de Langue & Composants Dynamiques

Nouveau : Accédez à la langue courante pour localiser dynamiquement vos composants.

import {
  getCurrentLanguage,
  detectCurrentLanguage,
  watchLanguageChanges,
} from "love-on-the-route";

// Détection simple de la langue courante
const currentLang = getCurrentLanguage();
console.log(currentLang); // 'fr', 'en', etc.

// Détection avec langues supportées
const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // Avec fallback garanti

// Surveiller les changements de langue (composants réactifs)
const unsubscribe = watchLanguageChanges((newLang) => {
  console.log("Langue changée vers :", newLang);
  // Mettre Ă  jour votre composant...
});

// ArrĂŞter la surveillance
unsubscribe();

Exemple : Composant Localisé

import { getCurrentLanguage, watchLanguageChanges } from "love-on-the-route";

class ComposantLocalise {
  private currentLang: string;
  private translations = {
    en: { welcome: "Welcome", button: "Click me" },
    fr: { welcome: "Bienvenue", button: "Cliquez-moi" },
    es: { welcome: "Bienvenido", button: "Haz clic" },
  };

  constructor() {
    this.currentLang = getCurrentLanguage();
    this.render();

    // Mise Ă  jour automatique lors des changements de langue
    watchLanguageChanges((newLang) => {
      this.currentLang = newLang;
      this.render();
    });
  }

  render() {
    const t = this.translations[this.currentLang] || this.translations.fr;
    const container = document.createElement("div");
    container.innerHTML = `
      <h1>${t.welcome}</h1>
      <button>${t.button}</button>
    `;
    return container;
  }
}

Configuration multilingue avec logo

const { routes, languages, isMultilingual } =
  autoDiscoverPagesIntelligent(pages);

if (isMultilingual) {
  const getHomePath = (): string => {
    const currentPath = window.location.pathname;
    const currentLang = languages.find((lang) =>
      currentPath.startsWith(`/${lang}`)
    );
    return `/${currentLang || languages[0]}`;
  };

  let nav: LoveNav;
  const updateNavigation = () => {
    const currentLanguageRoutes = filterRoutesByCurrentLanguage(
      routes,
      languages
    );
    const navRoutes = currentLanguageRoutes.map((r) => ({
      path: r.path,
      title: r.title,
    }));

    if (nav) {
      nav.updateRoutes(navRoutes);
      // Mettre Ă  jour le href du logo pour la langue courante
      nav.updateLogo({
        html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
        href: getHomePath(),
        replacesHome: true,
        linkClass: "logo-link",
      });
    } else {
      nav = new LoveNav(navRoutes, {
        logo: {
          html: `<img src="/logo.svg" alt="Mon Site" width="120" height="40">`,
          href: getHomePath(),
          replacesHome: true,
          linkClass: "logo-link",
        },
      });
      document.body.appendChild(nav.render());
    }
  };

  updateNavigation();
  window.addEventListener("routeChanged", updateNavigation);
}

API Logo

interface LogoConfig {
  html: string; // HTML du logo (img, svg, text...)
  href?: string; // URL de destination (défaut: "/")
  replacesHome?: boolean; // Si true, remplace les liens "Home"/"Accueil" (défaut: false)
  containerClass?: string; // Classe CSS du conteneur
  linkClass?: string; // Classe CSS du lien
}

Méthodes disponibles :

  • nav.render() - Rendu normal (logo intĂ©grĂ© si configurĂ©)
  • nav.renderSeparate() - Rendu sĂ©parĂ© { logo?, nav }
  • nav.updateLogo(logoConfig) - Mise Ă  jour dynamique du logo

Option 4 : Filtrage manuel des routes home (quand le logo est géré séparément)

Si vous gérez votre logo séparément de LoveNav, vous pouvez filtrer manuellement les routes home :

const updateNavigation = () => {
  const currentLanguageRoutes = filterRoutesByCurrentLanguage(
    routes,
    languages
  );

  // Filtrer manuellement les routes home
  const navRoutes = currentLanguageRoutes
    .filter((r) => {
      const path = r.path.toLowerCase();
      const title = r.title.toLowerCase();

      // Exclure les routes home
      const isHomeRoute =
        path === "/" ||
        path.match(/^\/[a-z]{2}$/) || // /en, /fr, etc.
        title === "home" ||
        title === "accueil";

      return !isHomeRoute;
    })
    .map((r) => ({ path: r.path, title: r.title }));

  // Votre gestion séparée du logo
  logoLink.href = getHomePath();

  if (nav) {
    nav.updateRoutes(navRoutes);
  } else {
    nav = new LoveNav(navRoutes, {
      containerClass: "main-navigation",
      linkClass: "nav-link",
      activeClass: "active",
    });
    navContainer.appendChild(nav.render());
  }
};

Cette approche est utile quand :

  • Vous avez une configuration de logo complexe
  • Vous voulez un contrĂ´le total sur le positionnement du logo
  • Vous utilisez un conteneur de navigation sĂ©parĂ©

Hooks de Langue

Accédez et réagissez aux changements de langue dans vos composants avec ces 3 hooks simples :

1. Récupération simple de la langue actuelle

import { getCurrentLanguage } from "love-on-the-route";

const lang = getCurrentLanguage();
console.log(lang); // 'fr', 'en', etc.

2. Récupération avec langues supportées

import { detectCurrentLanguage } from "love-on-the-route";

const lang = detectCurrentLanguage(["en", "fr", "es"]);
console.log(lang); // Avec fallback garanti

3. 🆕 Surveillance des changements de langue

import { watchLanguageChanges } from "love-on-the-route";

// Écouter les changements
const unsubscribe = watchLanguageChanges((newLang) => {
  console.log("Langue changée vers :", newLang);
  // Mettre Ă  jour votre composant...
});

// Arrêter l'écoute
unsubscribe();

Pourquoi Love On The Route ?

  • Ultra simple - 4 lignes pour dĂ©marrer
  • Zero CSS imposĂ© - Votre design, vos classes
  • File-based routing - Comme Next.js mais vanilla
  • SEO ready - Meta tags automatiques
  • TypeScript - Support complet
  • Multilingue - DĂ©tection automatique
  • Noms flexibles - Pas de contraintes sur les noms de fichiers

Love On The Route - The 3 annoying things solved! / Les 3 trucs chiants en moins !

From connection lines to routing lines 🎶 | Inspired by Kormak

Packages

 
 
 

Contributors