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

@qite/tide-components

v1.0.15

Published

React components for Tide (booking, product availability, form, ...)

Readme

@qite/tide-components

React component library for Tide. What started as a single booking wizard component has grown into a full set of building blocks used across Tide-integrated sites: booking flows, search results, quick search modules (QSM), dynamic forms, member login, and marketing/content blocks (navbar, header, footer, image grids, sliders, FAQ, ...).

Every component that talks to Tide's API is a plain React component — you own the page/routing, this library owns the Tide-specific UI and API calls.

Table of contents

Requirements

  • Node 20 (an .nvmrc is present; if you use fnm or nvm, your shell should pick it up automatically)
  • React 18 (react, react-dom ^18.2.0)
  • TypeScript 5.x recommended — the package ships its own .d.ts declarations

Installation

npm install @qite/tide-components

Then install the peer dependencies your package manager doesn't already resolve for you.

Dependencies

Peer dependencies (you must install these)

These are not bundled into the build output (they're externalized via rollup-plugin-peer-deps-external) — your app supplies its own copy, which also avoids duplicate-React-instance issues.

| Package | Version | Notes | |---|---|---| | react | ^18.2.0 | | | react-dom | ^18.2.0 | | | react-redux | ^9.2.0 | Needed even if you never touch Redux directly — BookingWizard, QSM and SearchResults create and provide their own stores internally | | react-router | ^6.30.3 | Only exercised by BookingWizard's standard (non-self-contained) flow — see Provider & router requirements | | @reduxjs/toolkit | ^2.8.2 | | | formik | ^2.2.9 | Used by TideForm and the booking traveler forms | | immer | ^9.0.5 | | | lodash | ^4.17.21 | | | date-fns | ^4.1.0 | | | uuid | ^11.1.0 | | | @popperjs/core | ^2.10.2 | Positioning for date pickers / dropdowns | | react-popper | ^2.2.5 | | | flat | ^5.0.2 | | | @jsonurl/jsonurl | ^1.1.4 | Used to serialize booking state into the URL |

Bundled dependencies (no separate install needed)

These ship inside @qite/tide-components's build output already, so you don't add them to your own package.json:

  • he, jwt-decode, react-html-comment, react-router-dom, signalr-no-jquery (used internally for live flight-price updates in SearchResults), yup
  • The Tide API client runtime code

A note on @qite/tide-client and TypeScript

The Tide API client, @qite/tide-client, is bundled into the build at runtime — you do not need to install it for the components to work. However, several public prop/settings types reference its types directly (Login's tideClientConfig: TideClientConfig, Navbar's member?: MemberInfo, QSM's searchConfigurations, TideForm's contexts, SearchResults's tideConnection, ...). If you use TypeScript, add @qite/tide-client as a dev dependency too, matching the version this package was built against, so those types resolve cleanly in your build. It isn't needed at runtime — only at compile time.

Authentication (apiKey / apiUrl)

Components that call Tide's API accept a Tide API key and host, under slightly different names depending on the component:

| Component | Where | |---|---| | BookingProduct, BookingWizard, TideForm | settings.apiKey / settings.apiUrl | | SearchResults | configuration.tideConnection.apiKey / .host | | Login | tideClientConfig.apiKey / .host |

As of @qite/[email protected], that API key is exchanged transparently for a short-lived access token behind the scenes (token exchange, caching and refresh all happen inside the client) — you don't need to write any auth code yourself, just provide a valid key and host. See API_KEY_MIGRATION.md for the full background on that model, including how a member/customer's own login token (from Login) takes over from the service API key once someone is logged in.

Provider & router requirements at a glance

Every component below is self-contained regarding state — none of them expect you to pass in a Redux store or context. The only two things you may need to add yourself:

| Component | Needs <BrowserRouter> around it? | Sets up its own Redux store internally? | |---|---|---| | BookingWizard | Yes (standard flow — set settings.skipRouter = true to opt out, matching the self-contained bundle's behavior) | Yes | | QSM | No | Yes | | SearchResults | No | Yes | | BookingProduct | No | No (context only) | | TideForm | No | No (context only) | | Login | No | No | | All content/marketing components | No | No |

Components

Booking, search & forms

BookingProduct

A single bookable product (accommodation, excursion, ...) with its own date/room picker and pricing, typically shown in a list or product detail page.

<BookingProduct
  productCode="PTFSGROUPTOUR"
  productName="Soft Rock Hotel"
  rating={3.5}
  settings={{
    apiKey, apiUrl,
    officeId: 1,
    catalogueId: 1,
    language: 'en-GB',
    basePath: '/boeken',
    priceMode: 0,
    includeFlights: true,
    displayMode: 'calendar'
  }}
/>

Key settings fields: officeId, catalogueId, basePath, language, priceMode (required); apiKey/apiUrl, agentId, includeFlights, displayMode ('list' | 'calendar'), disableRooms, mainIcon, customTranslationsUrl/customTranslations, isOffer, alternativeActionText/alternativeAction (optional).

BookingWizard

The full multi-step booking flow (options → travelers → summary → confirmation) for a product, driven by routes.

<BrowserRouter>
  <Routes>
    <Route
      path="/boeken/*"
      element={
        <BookingWizard
          productCode="HTFSSOFTROCK"
          productName="Soft Rock Hotel"
          thumbnailUrl="https://example.com/thumb.jpg"
          settings={{
            apiKey, apiUrl,
            officeId: 1,
            language: 'en-GB',
            basePath: '/boeken',
            productPath: '/',
            bookingOptions: { b2b: { entryStatus: 0 }, b2b2c: { entryStatus: 0 }, b2c: { entryStatus: 0 } },
            roomOptions: { pathSuffix: '/kamers' },
            flightOptions: { pathSuffix: '/vluchten' },
            options: { pathSuffix: '/extra' },
            travellers: { pathSuffix: '/reizigers' },
            summary: { pathSuffix: '/samenvatting', checkboxes: [] },
            confirmation: { pathSuffix: '/bevestiging' },
            error: { pathSuffix: '/mislukt' },
            companyContactEmail: '[email protected]',
            companyContactPhone: '+32 000 00 00 00',
            showProductCardRating: false,
            showSidebarDeposit: true
          }}
        />
      }
    />
  </Routes>
</BrowserRouter>

Every step (roomOptions, flightOptions, options, travellers, summary, confirmation, error) is a required config block with at least a pathSuffix. Other notable settings fields: skipBasePathInRouting (omit basePath when building internal nav URLs — needed when the host already scopes routing itself, e.g. under a Gatsby basePath), skipRouter (bypass react-router entirely, see below), translationFiles/customTranslationsUrl, hideAgentSelection/agentRequired/agentAdressId, enableVoucher, maxChildAge (defaults to 17), maxBabyAge (defaults to 1).

Set skipRouter: true to run BookingWizard without a <BrowserRouter> ancestor — step transitions are then driven by Redux state instead of routes (this is exactly what the self-contained bundle does).

Traveler age thresholds. maxChildAge and maxBabyAge decide how each booked traveler is presented and validated in the travelers step: above maxChildAge is an adult, at or below it a child, and at or below maxBabyAge a baby. A baby gets its own label instead of the child one, and its date of birth has to be under maxBabyAge + 1 on the departure date — so with the default of 1, "younger than 2 when the trip starts". Children are checked against the return date instead, as before. Both settings are also available as attributes (maxChildAge, maxBabyAge) on the self-contained booking-wizard bundle.

These thresholds are compared against the age of the pax in the booking request — the childAges your search sent — not against what the traveler types in. A site that books babies as age 2 therefore needs maxBabyAge: 2 for them to be recognized, which also loosens the date-of-birth rule to "younger than 3 on departure".

Translation overrides (translationFiles, customTranslationsUrl) are deep-merged over the built-in translations, so you only supply the keys you want to replace, including inside nested groups such as TRAVELERS_FORM.VALIDATION. Before 1.4.131 the merge was one level deep per section: overriding a nested group meant every built-in key you left out of it went missing, so if you worked around that by copying a whole group into your override file, you can trim it back to just your own keys.

QSM (Quick Search Module)

A search form (flights, hotels, round trips, group tours) that hands its result off via onSubmit — QSM itself never calls the booking/search-results API, it just collects and validates search criteria.

<QSM
  configuration={{
    searchConfigurations,
    askTravelers: true,
    askNationality: true,
    nationalities,
    allowOneWay: true,
    allowRoundtrip: true,
    departureAirport: { fieldKey: 'selectedDepartureAirport', label: 'Departure', options: originAirports, autoComplete: true },
    destinationAirport: { fieldKey: 'selectedDestinationAirport', label: 'Destination', options: destinationAirports },
    onSubmit: (data) => navigate(buildSearchResultsUrl(data)),
    submitIcon: <SearchIcon />
  }}
/>

SearchResults

Renders search results (flights, hotels, round trips, packages) for a given search configuration and lets the user start a booking.

<SearchResults
  configuration={{
    tideConnection: { host: apiUrl, apiKey, catalogueIds: [1], officeId: 1 },
    searchConfiguration,
    showFilters: true,
    showFlightResults: true,
    showTabViews: true
  }}
  onBookingStarted={() => setIsBooking(true)}
/>

Flight results use SignalR for live price updates (signalr-no-jquery, bundled — no setup required on your side).

TideForm

Renders a Tide-configured dynamic web form (e.g. a contact/quote request form tied to a product).

<TideForm
  configuration={{
    id: 1,
    languageCode: 'en-GB',
    apiKey, apiUrl,
    contexts: [{ key: 'product', identifier: 'ETFSSTARGAZE' }],
    initialValues: { travellers: { traveller_counts: { adult_count: 2, child_count: 0 } } }
  }}
/>

Login

Member login / password reset / account confirmation. Login doesn't manage navigation between those three modes itself — you flip between them with the boolean props based on your own routing.

const [member, setMember] = useState<MemberInfo>();

<Login
  tideClientConfig={{ host: apiUrl, apiKey, catalogueIds: [1] }}
  portalId={0}
  languageCode="en-GB"
  isLoginPage={true}
  isResetPassword={false}
  isMemberConfirmation={false}
  member={member}
  setMember={setMember}
  handleBackToHome={() => navigate('/')}
  handleBackToLogin={() => navigate('/login')}
/>

Once setMember receives a logged-in MemberInfo, pass that same object into Navbar's member prop to reflect the logged-in state in your header.

Content & marketing

These are presentational — plain props in, JSX out, no Tide API calls, no context or store required — unless noted otherwise.

Header

Hero/banner section with a video, image, or slider background.

<Header
  media={{ type: 'image', src: '/hero.jpg', alt: 'Sunset over the coast' }}
  title="Explore the world with us"
  description="Discover breathtaking destinations, curated just for you."
  showButton
  buttonText="See offers"
  onButtonClick={() => navigate('/offers')}
/>

Navbar

Site navigation, including language switcher and login/logout state. Navbar is purely presentational — you own fetching/holding MemberInfo (e.g. from Login) and pass it in.

<Navbar
  logo={<Logo />}
  topLinks={topLinks}
  items={navItems}
  language={language}
  languages={languages}
  onLanguageChange={setLanguage}
  onSearch={(query) => navigate(`/search?q=${query}`)}
  member={member}
  onLogin={() => navigate('/login')}
  onLogout={() => setMember(undefined)}
/>

Footer

Site footer with columns, social links and payment icons. Every prop is optional — <Footer /> renders sensible defaults.

<Footer />

ImageCardGrid

A grid of image cards with an optional call-to-action per card.

<ImageCardGrid
  title="Popular destinations"
  columns={4}
  cards={[{ image: '/santorini.jpg', title: 'Santorini', buttonText: 'Book now', onButtonClick: (card) => navigate(`/product/${card.title}`) }]}
/>

ImageWithTextSection

Alternating image + two-column text sections, e.g. for describing activities.

<ImageWithTextSection
  title="Activities"
  cards={[{
    imageSrc: '/activity.jpg', imageAlt: 'Hiking trail',
    title: 'Discover Santorini', section1Title: 'The activity', section1Text: '...',
    section2Title: 'Details', section2Text: '...', buttonText: 'Book now'
  }]}
/>

Slider

A simple image carousel; also used internally by Header for media.type: 'slider'.

<Slider images={['/1.jpg', '/2.jpg', '/3.jpg']} mode="auto" intervalMs={4000} />

PhotoGallery

A titled image gallery with a lightbox.

<PhotoGallery
  title="Project gallery"
  images={[{ src: '/lake.jpg', alt: 'Mountain lake', caption: 'Morning hike' }]}
/>

Breadcrumbs

<Breadcrumbs items={[{ href: '/', label: 'Home' }, { href: '/products', label: 'Products' }, { label: 'Santorini', isCurrent: true }]} />

FAQ

Renders a list of question/answer pairs as an accordion.

<FAQ title="Frequently asked questions" items={[{ question: 'How do I cancel?', answer: <p>Contact us at least 48h in advance.</p> }]} />

FeaturedTrips

A row of highlighted trip cards.

<FeaturedTrips
  title="Featured trips"
  cards={[{ imageSrc: '/santorini.jpg', imageAlt: 'Santorini', title: 'Santorini getaway', location: 'Greece', onButtonClick: () => navigate('/santorini') }]}
/>

Spinner

Loading indicator, localized via the SearchResultsConfigurationContext when rendered inside SearchResults (falls back to English otherwise).

{isLoading ? <Spinner /> : <SearchResults configuration={configuration} />}

ItineraryMapView

An interactive multi-destination itinerary builder with a map. Loads Leaflet's CSS/JS from a CDN at runtime, so it needs outbound network access to unpkg.com and map tile servers — all itinerary state is local to the component; callbacks are notifications, your app decides what to do with them.

<ItineraryMapView
  searchResults={searchResults}
  onSearchChange={(query) => setSearchResults(searchDestinations(query))}
  onAddDestination={(name) => addDestination(name)}
  onContinue={() => navigate('/booking')}
/>

Self-contained (non-React) bundles

For host pages that aren't React apps, selfcontained/ ships pre-bundled scripts you drop in with a <script> tag; each looks for a specific container element, reads its configuration from that element's attributes (or the URL query string), and mounts itself — no React setup needed on the host page.

Three of these are published to Tide's CDN, each under its own per-version path (plus a latest alias):

| Bundle | Mounts into | CDN component | CDN path | |---|---|---|---| | form | #tide-form | form | https://cdn.tidesoftware.be/components/form/{version}/ (or /latest/) | | booking-wizard | #tide-booking | booking | https://cdn.tidesoftware.be/components/booking/{version}/ (or /latest/) | | booking-product | #tide-product | product | https://cdn.tidesoftware.be/components/product/{version}/ (or /latest/) |

Each CDN version directory contains bundle.js, bundle.css, bundle.js.LICENSE.txt, variables.css, and a changelog.txt listing what changed in that version — sourced from the matching changelog.txt in each bundle's folder under selfcontained/ in this repo.

Configuration is read from attributes on the settings element (plus the URL query string for a couple of fields). booking-wizard additionally runs with skipRouter: true in this mode, so it needs no <BrowserRouter>; form and booking-product don't use routing at all.

Framework-specific notes

Gatsby (or any SSR framework): when using BookingWizard on a Gatsby v5+ site:

  • Use React 18 and react-router-dom@6.
  • Import only from @qite/tide-components at the top level and keep the actual render client-side (e.g. behind typeof window !== 'undefined', or in a client-only wrapper) — signalr-no-jquery touches window and will error if evaluated during SSR/build.
  • Surround <BookingWizard> with <BrowserRouter basename={basePath}> to handle its internal routing.
  • Set skipBasePathInRouting: true in settings to avoid duplicate base paths in the wizard's internal navigation.

Working on this library itself, rather than consuming it? See DEVELOPMENT.md for local setup (not included in the published package).