npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bit.rhplus/device

v0.0.13

Published

Modul pro detekci a přepínání mezi mobilní a desktop verzí aplikace s podporou pro iOS "Request Desktop Site".

Readme

Device Detection Module

Modul pro detekci a přepínání mezi mobilní a desktop verzí aplikace s podporou pro iOS "Request Desktop Site".

Problém, který řeší

iOS Safari/Chrome chování

Když uživatel na iOS použije "Request Desktop Site":

  • Starý systém: User-Agent zůstává mobilní → zobrazuje se stále mobilní verze
  • Nový systém: Kombinuje User-Agent + viewport šířku → zobrazí se desktop verze

Android Chrome chování

  • ✅ User-Agent se mění při přepnutí na desktop verzi → funguje správně

Nová funkcionalita

1. Kombinovaná detekce

// Priorita detekce:
1. localStorage override (manuální volba uživatele)
2. Viewport šířka (>= 768px) + User-Agent
3. Standardní User-Agent detekce

2. Dynamická reakce na změny

  • Poslouchá window.resize události
  • Automaticky přepne verzi při změně velikosti viewportu
  • Řeší iOS "Request Desktop Site" změny

3. Manuální override

Uživatel si může sám vybrat, kterou verzi chce zobrazit:

  • localStorage persistence (přetrvává po refreshi)
  • Možnost vrátit se k automatické detekci

Použití

Basic - Detekce typu zařízení

import { useDeviceType } from '@bit.rhplus/device';

function MyComponent() {
  const deviceType = useDeviceType(); // 'mobile' nebo 'desktop'

  return (
    <div>
      Aktuální verze: {deviceType}
    </div>
  );
}

Podmíněné zobrazení komponent

import { Desktop, Mobile } from '@bit.rhplus/device';

function MyPage() {
  return (
    <>
      <Desktop>
        <DesktopLayout />
      </Desktop>

      <Mobile>
        <MobileLayout />
      </Mobile>
    </>
  );
}

Manuální přepínání verzí

import { useDeviceControl } from '@bit.rhplus/device';

function SettingsPage() {
  const { deviceType, setDeviceOverride } = useDeviceControl();

  return (
    <div>
      <p>Aktuální verze: {deviceType}</p>

      <button onClick={() => setDeviceOverride('mobile')}>
        Mobilní verze
      </button>

      <button onClick={() => setDeviceOverride('desktop')}>
        Desktop verze
      </button>

      <button onClick={() => setDeviceOverride('auto')}>
        Automatická detekce
      </button>
    </div>
  );
}

UI komponenty pro přepínání

Varianta 1: Select dropdown

import { DeviceToggle } from '@bit.rhplus/device';

function SettingsPage() {
  return (
    <div>
      <h2>Nastavení zobrazení</h2>
      <DeviceToggle variant="select" />
    </div>
  );
}

Varianta 2: Toggle switch

import { DeviceToggle } from '@bit.rhplus/device';

function Header() {
  return (
    <div className="header">
      <DeviceToggle variant="toggle" />
    </div>
  );
}

Varianta 3: Button group

import { DeviceToggle } from '@bit.rhplus/device';

function SettingsPage() {
  return (
    <div>
      <DeviceToggle variant="button" />
    </div>
  );
}

Varianta 4: Plovoucí tlačítko (Development)

import { FloatingDeviceToggle } from '@bit.rhplus/device';

function PlatformApp() {
  return (
    <div>
      {/* Vaše aplikace */}

      {/* Plovoucí tlačítko v pravém dolním rohu */}
      <FloatingDeviceToggle />

      {/* Nebo zobrazit i v produkci */}
      <FloatingDeviceToggle showInProduction={true} />
    </div>
  );
}

Příklad integrace do PlatformApp.jsx

import { useDeviceType, FloatingDeviceToggle } from '@bit.rhplus/device';

const PlatformApp = (props) => {
  const deviceType = useDeviceType();

  return (
    <StyleProvider hashPriority="high">
      <App routes={routes}>
        <div className="app-container">
          {/* Sidebar pouze pro desktop */}
          {deviceType === 'desktop' && (
            <Sidebar
              modules={safeModules}
              // ... props
            />
          )}

          {/* Hlavní obsah */}
          <div className={`app-content ${deviceType === 'mobile' ? 'app-content-full-width' : ''}`}>
            <View
              url="/"
              main={true}
              // ... props
            >
              <UpdateNotification />
            </View>
          </div>
        </div>

        {/* Development: Plovoucí tlačítko pro testování */}
        {process.env.NODE_ENV === 'development' && (
          <FloatingDeviceToggle />
        )}
      </App>
    </StyleProvider>
  );
};

Testování na iOS

Před úpravou (problém):

  1. Otevři aplikaci na iOS Safari/Chrome
  2. Klikni na "AA" → "Request Desktop Site"
  3. ❌ Aplikace stále zobrazuje mobilní verzi

Po úpravě (řešení):

  1. Otevři aplikaci na iOS Safari/Chrome
  2. Klikni na "AA" → "Request Desktop Site"
  3. ✅ Aplikace automaticky přepne na desktop verzi (viewport >= 768px)

Manuální override (nová funkce):

  1. Otevři aplikaci na iOS
  2. Klikni na plovoucí tlačítko (development) nebo přejdi do nastavení
  3. Vyber "Desktop verze"
  4. ✅ Aplikace zobrazí desktop verzi bez ohledu na viewport
  5. Preference se uloží do localStorage (přetrvává po refreshi)

Technické detaily

DeviceProvider

<DeviceProvider deviceType="auto">
  {/* Aplikace */}
</DeviceProvider>

Props:

  • deviceType: 'auto' | 'mobile' | 'desktop'
    • 'auto' (default): Automatická detekce
    • 'mobile': Vždy mobilní verze
    • 'desktop': Vždy desktop verze

Context hodnota

{
  deviceType: 'mobile' | 'desktop',
  setDeviceOverride: (type: 'mobile' | 'desktop' | 'auto' | null) => void
}

localStorage klíč

'rhplus_device_override'

Hodnoty:

  • 'mobile': Uživatel preferuje mobilní verzi
  • 'desktop': Uživatel preferuje desktop verzi
  • null nebo undefined: Automatická detekce

Viewport breakpoint

const VIEWPORT_BREAKPOINT = 768; // px

Pokud window.innerWidth >= 768px a zařízení má mobilní User-Agent, preferuje se desktop verze (iOS use case).

API Reference

Hooks

useDeviceType()

Vrací aktuální typ zařízení.

const deviceType = useDeviceType(); // 'mobile' | 'desktop'

useDeviceControl()

Vrací deviceType + funkci pro manuální override.

const { deviceType, setDeviceOverride } = useDeviceControl();

setDeviceOverride('desktop'); // Přepne na desktop
setDeviceOverride('mobile');  // Přepne na mobile
setDeviceOverride('auto');    // Automatická detekce
setDeviceOverride(null);      // Automatická detekce

Komponenty

<Desktop>{children}</Desktop>

Zobrazí obsah pouze na desktop verzi.

<Mobile>{children}</Mobile>

Zobrazí obsah pouze na mobilní verzi.

<ShowAt device="mobile|desktop">{children}</ShowAt>

Podmíněné zobrazení na základě typu zařízení.

<DeviceToggle variant="button|select|toggle" />

UI komponenta pro přepínání verzí.

Props:

  • variant: 'button' | 'select' | 'toggle'
  • className: CSS třída pro custom styling

<FloatingDeviceToggle showInProduction={false} />

Plovoucí tlačítko v pravém dolním rohu.

Props:

  • showInProduction: boolean - Zobrazit i v produkci (default: false)

Migrace z původního systému

Před (starý kód):

const deviceType = useDeviceType(); // Pouze User-Agent detekce

Po (nový kód):

const deviceType = useDeviceType(); // User-Agent + viewport detekce

Žádná změna v kódu není nutná! API zůstává zpětně kompatibilní.

Nové funkce (volitelné):

// Manuální override
const { deviceType, setDeviceOverride } = useDeviceControl();

// UI komponenty pro přepínání
<DeviceToggle variant="select" />
<FloatingDeviceToggle />

FAQ

Q: Proč iOS "Request Desktop Site" nefungoval?

A: iOS nemění User-Agent string při přepnutí na desktop režim. Nový systém kombinuje User-Agent s viewport šířkou.

Q: Jak změnit breakpoint pro desktop verzi?

A: Uprav konstantu VIEWPORT_BREAKPOINT v DeviceProvider.jsx (default: 768px).

Q: Jak přidat manuální přepínač do nastavení?

A: Použij <DeviceToggle variant="select" /> komponentu.

Q: Preference se neuloží po refreshi

A: Zkontroluj, že localStorage není blokován v prohlížeči.

Q: Chci zobrazit plovoucí tlačítko i v produkci

A: Použij <FloatingDeviceToggle showInProduction={true} />.

Optimalizace

  • ✅ Memoizace context hodnoty (useMemo)
  • ✅ Debounce resize eventů (inherent v React state updates)
  • ✅ localStorage cache pro persistence
  • ✅ Cleanup listeners při unmount
  • ✅ Minimální re-rendery (pouze při změně deviceType)

Browser podpora

  • ✅ iOS Safari 13+
  • ✅ iOS Chrome 13+
  • ✅ Android Chrome 80+
  • ✅ Desktop Chrome/Firefox/Safari/Edge
  • ✅ localStorage support required