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

@movingwaldo/chatbot

v0.2.0

Published

MovingWaldo chatbot

Readme

@movingwaldo/chatbot

React library to embed the MovingWaldo chatbot: a floating chat bubble launcher and the chatbot panel (iframe), plus a hook to control and observe it.

Dependencies

  • React 19+

Install

yarn add @movingwaldo/chatbot

Import the stylesheet ONCE (e.g. in your app entry point):

import '@movingwaldo/chatbot/styles.css'

Public API

Two components are exposed:

  • MovingWaldoChatbot - renders the chat bubble launcher and the chatbot panel. If it is not already wrapped in a MovingWaldoChatbotProvider, it self-wraps in one, so it works standalone.
  • MovingWaldoChatbotProvider - optional. Only needed if you want to call useMovingWaldoChatbot() from your own components.

And one hook: useMovingWaldoChatbot().

Usage

Standalone

import { MovingWaldoChatbot } from '@movingwaldo/chatbot'

export const App = () => <MovingWaldoChatbot config={{ lang: 'fr' }} />

With the provider (to use the hook)

import {
  MovingWaldoChatbot,
  MovingWaldoChatbotProvider,
  useMovingWaldoChatbot,
} from '@movingwaldo/chatbot'

const Launcher = () => {
  const { open, status } = useMovingWaldoChatbot()

  return (
    <button onClick={open}>Chat with us (status: {status})</button>
  )
}

export const App = () => (
  <MovingWaldoChatbotProvider config={{ lang: 'en', url: 'https://booking.movingwaldo.com' }}>
    <Launcher />
    <MovingWaldoChatbot />
  </MovingWaldoChatbotProvider>
)

Configuration

All config lives under a single object, accepted by both the provider and the chatbot. Config on MovingWaldoChatbot overrides config on the provider.

| Field | Type | Default | Notes | | ---------------- | ---------------- | ------------------------------------ | --------------------------------------------------------------------- | | lang | 'en' \| 'fr' | 'en' | Selects the language. | | url | string | 'https://booking.movingwaldo.com' | /embed/chatbot/v1 is appended. | | primaryColor | string | 'rgb(250, 55, 44)' (MovingWaldo red) | Any CSS color. | | moveId | string | empty (not provided) | UUID v4 of the move (job) to associate the conversation with. Any non-UUID-v4 value is silently ignored. Runtime-updatable via setConfig({ moveId }). | | hideButton | boolean | false | Hides the floating launcher bubble. | | position | 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' | empty (CSS default: bottom-right) | Anchors both the bubble and the panel to a corner. Leave it unset to rely on CSS instead (see below). | | buttonSize | number \| string | empty (CSS default: 56) | Diameter of the launcher bubble. A number is px; a string is any CSS length (e.g. '4rem'). Sets --mwcb-button-size. Leave empty to rely on CSS. | | chatWindowSize | { width: number \| string; height: number \| string } | empty (CSS default: 440×850) | Desktop chat window size. Each dimension is a number (px) or any CSS length string (e.g. '80vh'). Sets --mwcb-window-width / --mwcb-window-height. Ignored on mobile (full-screen). | | fullScreenChatWindowSize | { width: number \| string; height: number \| string } | calc(100vw - 2 * var(--mwcb-edge-distance)) × calc(100dvh - 2 * var(--mwcb-edge-distance)) | Desktop chat window size while fullscreen is active. Sets --mwcb-fs-window-width / --mwcb-fs-window-height. Ignored on mobile (already full-screen). Toggle fullscreen via the in-iframe header button or useMovingWaldoChatbot() (see below). | | edgeDistance | number \| string | empty (CSS default: 24) | Distance from the bubble/window to the viewport edge. A number is px; a string is any CSS length. Sets --mwcb-edge-distance. | | gapDistance | number \| string | empty (CSS default: 16) | Distance between the bubble and the chat window. A number is px; a string is any CSS length. Sets --mwcb-gap-distance. |

Every field except lang / url / primaryColor defaults to empty, in which case the library sets nothing and defers to the bundled CSS. This means you can either drive them from config, or leave them empty and control them with your own CSS (see below) - and clearing a field at runtime falls back to CSS.

Sizes and distances (buttonSize, chatWindowSize, fullScreenChatWindowSize, edgeDistance, gapDistance) accept either a number or any CSS length string. A bare number is interpreted as pixels - the number 56 and the unitless string '56' both become 56px - while any string carrying a unit or function is used verbatim, so relative units and functions work too:

<MovingWaldoChatbot
  config={{
    buttonSize: '4rem',
    chatWindowSize: { width: 'clamp(320px, 40vw, 560px)', height: '80vh' },
    edgeDistance: '2rem',
  }}
/>

Sizing & positioning with CSS

The sizes and distances are backed by CSS custom properties on the .mw-chatbot-root wrapper. When a config field is unset the variable is not written, so the stylesheet's fallback (the "CSS default" above) applies. Set the matching variable - or override the resolved property on the marker classes - to control everything from CSS:

| CSS variable | Config field | CSS default | | ----------------------- | ---------------- | ----------- | | --mwcb-button-size | buttonSize | 3.5rem (56px) | | --mwcb-window-width | chatWindowSize | 440px | | --mwcb-window-height | chatWindowSize | 850px | | --mwcb-fs-window-width | fullScreenChatWindowSize | calc(100vw - 2 * var(--mwcb-edge-distance)) | | --mwcb-fs-window-height | fullScreenChatWindowSize | calc(100dvh - 2 * var(--mwcb-edge-distance)) | | --mwcb-edge-distance | edgeDistance | 1.5rem (24px) | | --mwcb-gap-distance | gapDistance | 1rem (16px) | | --mwcb-color-primary | primaryColor | MovingWaldo red |

/* Re-size and re-space the chatbot entirely from CSS. */
.mw-chatbot-root {
  --mwcb-button-size: 64px;
  --mwcb-window-width: 500px;
  --mwcb-window-height: 720px;
  --mwcb-edge-distance: 2rem;
  --mwcb-gap-distance: 0.75rem;
}

On desktop the chat window is offset from its corner by edgeDistance + buttonSize + gapDistance so it clears the launcher bubble. When the button is hidden (hideButton: true) there is nothing to clear, so the window collapses that offset down to just edgeDistance, sitting closer to the edge. This is derived purely from the tokens above, so it behaves identically whether you drive the sizes/distances from config or from your own CSS.

Positioning defers to CSS the same way: by default both the launcher bubble and the chat panel are anchored bottom-right by scoped, overridable rules. Target the marker classes (.mw-chatbot-button for the bubble, .mw-chatbot-panel-card for the desktop panel):

/* Move the launcher bubble to the bottom-left instead. */
.mw-chatbot-button {
  right: auto;
  left: var(--mwcb-edge-distance);
}

/* Anchor the desktop panel to match (sm and up). */
@media (min-width: 40rem) {
  .mw-chatbot-panel-card {
    right: auto;
    left: var(--mwcb-edge-distance);
  }
}

Prefer config over CSS? Just set the fields instead:

<MovingWaldoChatbot
  config={{
    position: 'bottom-left',
    buttonSize: 64,
    chatWindowSize: { width: 500, height: 720 },
  }}
/>

useMovingWaldoChatbot()

const {
  status,
  config,
  setConfig,
  isOpen,
  open,
  close,
  toggle,
  isFullScreen,
  setFullScreen,
  toggleFullScreen,
  type,
  clear
} = useMovingWaldoChatbot()
  • status: 'stalled' | 'loading' | 'loaded' | 'error'
  • config: the resolved configuration
  • setConfig(partial): merges a partial config on top of the current one at
  • isOpen: return true if the chat window is opened
  • open(), close(), toggle(): open/hide chatbot window
  • isFullScreen: return true if the chat window is in fullscreen mode
  • setFullScreen(next), toggleFullScreen(): control fullscreen mode programmatically. Fullscreen resizes the desktop panel to fullScreenChatWindowSize with a smooth transition (disabled under prefers-reduced-motion). The in-iframe header also exposes a fullscreen toggle button (shown only when embedded, hidden on mobile); both stay in sync.
  • type(text: string): Sends text as the customer - opens the panel if closed and submits text as a user message in the conversation.
  • clear(): Clears the current conversation without a confirmation dialog - closes the panel, expires the conversation-id cookie, drops the in-memory id, and resets the thread to greeting-only. The next message afterwards starts a fresh conversation. Mirrors the "Start over" UI action but is confirmation-free and programmatic.

MovingWaldoChatbot also accepts a controlled open prop.