Skip to main content
/
/
/
FloatingMessage

FloatingMessage

import FloatingMessage from "@intility/bifrost-react/FloatingMessage";

A floating message is suitable only for temporary low-priority feedback.

  • Pops up in bottom right corner (configurable)
  • Disappears after 7 seconds (configurable)
  • Displays up to 3 stacked messages (configurable)
  • Stays visible as long as mouse cursor is hovering over the message
  • Read aloud by screen readers when opened

Setup

Wrap your app content in the <FloatingMessage> provider, then use useFloatingMessage() hook to access the showFloatingMessage(message, options) method.

Only use a single <FloatingMessage> in your app (to avoid any message overlap). The useFloatingMessage hook will then be available to use in any descendant component.

Ideally place it inside <Nav> to avoid overlapping with the menus, but if you need to show messages from code residing in the menu (or outside) you will need to move the provider higher in the hierarchy. If you place messages top and/or left you then need to manually prevent overlapping with the menu with some custom CSS.

A typical setup might look something like this:

import Nav from "@intility/bifrost-react/Nav"; import FloatingMessage from "@intility/bifrost-react/FloatingMessage"; import useFloatingMessage from "@intility/bifrost-react/hooks/useFloatingMessage"; function MyComponent() { const { showFloatingMessage } = useFloatingMessage(); // probably inside a click handler or other event showFloatingMessage("Hello world"); } // in your main / layout file <Nav> <FloatingMessage> Rest of your app here <MyComponent /> </FloatingMessage> </Nav>;
import Nav from "@intility/bifrost-react/Nav"; import FloatingMessage from "@intility/bifrost-react/FloatingMessage"; import useFloatingMessage from "@intility/bifrost-react/hooks/useFloatingMessage"; function MyComponent() { const { showFloatingMessage } = useFloatingMessage(); // probably inside a click handler or other event showFloatingMessage("Hello world"); } // in your main / layout file <Nav> <FloatingMessage> Rest of your app here <MyComponent /> </FloatingMessage> </Nav>;

The showFloatingMessage(message, options)method accepts two parameters:

  • message simple string or tsx
  • options optional object, forwarded as props for <Message>

Demo

const { showFloatingMessage } = useFloatingMessage(); // Show message showFloatingMessage("Message text"); // Without icon showFloatingMessage("Message text", { state: "success", noIcon: true, }); // Formatted alert showFloatingMessage( <> Message text <em>with formatting</em> </>, { state: "alert" }, );
const { showFloatingMessage } = useFloatingMessage(); // Show message showFloatingMessage("Message text"); // Without icon showFloatingMessage("Message text", { state: "success", noIcon: true, }); // Formatted alert showFloatingMessage( <> Message text <em>with formatting</em> </>, { state: "alert" }, );

Timeout, max messages and position

A message will be shown for 7000ms (seven seconds) by default, but can be configured through the timeout prop. Set to 0 for no timeout.

Up to 3 messages are displayed at a time by default, but can be configured through the max prop.

Default positioning is bottom right corner, but can be placed top and/or left as well.

<FloatingMessage timeout={10000} max={1} top left> [app content] </FloatingMessage>
<FloatingMessage timeout={10000} max={1} top left> [app content] </FloatingMessage>

Custom close button

We expose a removeFloatingMessage() method, but it will only be available for components rendered inside of a floating message, so you'll need to create a separate component and call the useFloatingMessage() hook from there:

function RemoveButton() { // removeFloatingMessage will only exist when nested inside a floating message const { removeFloatingMessage } = useFloatingMessage(); return <Button onClick={() => removeFloatingMessage?.()}>I got it</Button>; }
function RemoveButton() { // removeFloatingMessage will only exist when nested inside a floating message const { removeFloatingMessage } = useFloatingMessage(); return <Button onClick={() => removeFloatingMessage?.()}>I got it</Button>; }

Then you can pass your content including your RemoveButton component as JSX to the first parameter of showFloatingMessage().

showFloatingMessage( <> Message text <RemoveButton /> </>, );
showFloatingMessage( <> Message text <RemoveButton /> </>, );

Optionally disable the default x close button by passing undefined to the onClose option object (second parameter).

showFloatingMessage( <> Disable default onClose <RemoveButton /> </>, { onClose: undefined }, );
showFloatingMessage( <> Disable default onClose <RemoveButton /> </>, { onClose: undefined }, );

Sandbox

import Button from "@intility/bifrost-react/Button";
import FloatingMessage from "@intility/bifrost-react/FloatingMessage";
import useFloatingMessage from "@intility/bifrost-react/hooks/useFloatingMessage";

export default function FloatingMessageDemo() {
  return (
    <FloatingMessage>
      <MyComponent />
    </FloatingMessage>
  );
}

function MyComponent() {
  const { showFloatingMessage } = useFloatingMessage();

  return (
    <div className="bfl-inline">
      <Button onClick={() => showFloatingMessage(randomMessage())}>
        Show message
      </Button>
      <Button
        onClick={() =>
          showFloatingMessage(randomMessage(), {
            state: "success",
            noIcon: true,
          })
        }
      >
        Without icon
      </Button>
      <Button
        onClick={() =>
          showFloatingMessage(
            <>
              {randomMessage()} <em>with formatting</em>
            </>,
            {
              state: "alert",
            },
          )
        }
      >
        Formatted alert
      </Button>
    </div>
  );
}

const messages = [
  "He's a friend from work!",
  "Are you Thor, The God of Hammers?",
  "Oh my god, the hammer pulled you off?",
  "Is he though?",
  "We're not doing 'Get Help'",
  "Yeah, same. Hulk like fire, Thor like water.",
  "Asgard is not a place, it's a people.",
  "Piss off, ghost!",
  "I have been falling, for thirty minutes!",
  "We need one with cupholders.",
  "Yes! That's how that feels!",
];

function randomMessage() {
  return messages[Math.round(Math.random() * (messages.length - 1))];
}

Avoid overlapping

Floating/fixed elements at the bottom of the screen might overlap with a floating message, like the <BottomBar> component. You can place messages at top of the screen instead, or add some bottom space with some custom CSS that is only rendered when the BottomBar is.

import { useState } from "react";
import { faHome, faUser, faCog } from "@fortawesome/free-solid-svg-icons";
import FloatingMessage from "@intility/bifrost-react/FloatingMessage";
import useFloatingMessage from "@intility/bifrost-react/hooks/useFloatingMessage";
import Button from "@intility/bifrost-react/Button";
import BottomBar from "@intility/bifrost-react/BottomBar";
import Nav from "@intility/bifrost-react/Nav";

export default function () {
  const [page, setPage] = useState(10);
  return (
    <div
      className="page-demo"
      // simulate no sidebar for demo only
      style={{ "--bf-nav-side-width": "0px", minHeight: 300 }}
    >
      <FloatingMessage>
        <MyComponent />
      </FloatingMessage>
    </div>
  );
}

const MyComponent = () => {
  const { showFloatingMessage } = useFloatingMessage();
  const [displayBottomBar, setDisplayBottomBar] = useState(true);

  return (
    <>
      <div className="bfl-inline">
        <Button onClick={() => showFloatingMessage(randomMessage())}>
          Show message
        </Button>
        <Button onClick={() => setDisplayBottomBar(!displayBottomBar)}>
          Toggle bottom bar
        </Button>
      </div>
      {displayBottomBar && (
        // only render style tag when bottombar is rendered
        <>
          <style>{`
            .page-demo .bf-floatingmessage-stack { bottom: 40px }
          `}</style>
          <BottomBar>Some bottom bar content</BottomBar>
        </>
      )}
    </>
  );
};

const messages = [
  "He's a friend from work!",
  "Are you Thor, The God of Hammers?",
  "Oh my god, the hammer pulled you off?",
  "Is he though?",
  "We're not doing 'Get Help'",
  "Yeah, same. Hulk like fire, Thor like water.",
  "Asgard is not a place, it's a people.",
  "Piss off, ghost!",
  "I have been falling, for thirty minutes!",
  "We need one with cupholders.",
  "Yes! That's how that feels!",
];

function randomMessage() {
  return messages[Math.round(Math.random() * (messages.length - 1))];
}