Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add InlineToasts and useInlineToasts examples for inline, non-intrusive notifications in modals and drawer scenarios #933

Closed
cheton opened this issue Sep 28, 2024 · 0 comments · Fixed by #940

Comments

@cheton
Copy link
Member

cheton commented Sep 28, 2024

Usage

Use the useInlineToasts hook to manage inline toast notifications:

const { toasts, notify: notifyInlineToast } = useInlineToasts();

Trigger an inline toast notification:

notifyInlineToast({
  appearance: 'error',
  content: (
    <Text>{i18n._('An unexpected error has occurred.')}</Text>
  ),
  duration: undefined,
});

Display inline toasts on a Modal:

<Modal>
  <ModalContent>
    <InlineToastContainer>
      <InlineToasts toasts={toasts} />
    </InlineToastContainer>
    <ModalHeader />
    <ModalBody />
    <ModalFooter />
  </ModalContent>
</Modal>

Components

InlineToastContainer

import {
  Box,
} from '@tonic-ui/react';
import React, { forwardRef } from 'react';

const InlineToastContainer = forwardRef((inProps, ref) => (
  <Box
    ref={ref}
    flexDirection="column"
    alignItems="center"
    position="absolute"
    top="12x"
    left="50%"
    transform="translateX(-50%)"
    width="max-content"
    maxWidth="80%" // up to 80% of the modal or drawer width
    zIndex="toast"
    {...inProps}
  />
));

InlineToastContainer.displayName = 'InlineToastContainer';

export default InlineToastContainer;

InlineToasts

import {
  Toast,
  ToastController,
  ToastTransition,
  useColorStyle,
} from '@tonic-ui/react';
import React from 'react';
import { TransitionGroup } from 'react-transition-group';

const InlineToasts = inProps => {
  const [colorStyle] = useColorStyle();
  const {
    toasts = [],
    ...rest
  } = inProps;

  return (
    <TransitionGroup
      component={null} // Pass in `component={null}` to avoid a wrapping `<div>` element
    >
      {toasts.map(toast => (
        <ToastTransition
          key={toast?.id}
          in={true}
          unmountOnExit
        >
          <ToastController
            duration={toast?.duration}
            onClose={toast?.onClose}
          >
            <Toast
              appearance={toast?.appearance}
              isClosable={toast?.isClosable}
              onClose={toast?.onClose}
              sx={{
                mb: '2x',
                minWidth: 280, // The toast has a minimum width of 280 pixels
                width: 'fit-content',
                boxShadow: colorStyle.shadow.thin,
              }}
            >
              {toast?.content}
            </Toast>
          </ToastController>
        </ToastTransition>
      ))}
    </TransitionGroup>
  );
};

InlineToasts.displayName = 'InlineToasts';

export default InlineToasts;

useInlineToasts

import { ensurePositiveInteger } from 'ensure-type';
import { useCallback, useMemo, useState } from 'react';

const uniqueId = (() => {
  let id = 0;
  return () => {
    id += 1;
    return String(id);
  };
})();

const useInlineToasts = (options) => {
  const maxToasts = ensurePositiveInteger(options?.maxToasts);
  const [toasts, setToasts] = useState([]);

  const notify = useCallback((options) => {
    const {
      appearance,
      content,
      duration = null,
      isClosable = true,
    } = { ...options };

    setToasts(prevState => {
      const id = uniqueId();
      const onClose = () => {
        setToasts(toasts => toasts.filter(x => x.id !== id));
      };
      // You can decide how many toasts you want to show at the same time depending on your use case
      const nextState = [
        ...prevState.slice(maxToasts > 1 ? -(maxToasts - 1) : prevState.length),
        {
          id,
          appearance,
          content,
          duration,
          isClosable,
          onClose,
        },
      ];
      return nextState;
    });
  }, [maxToasts]);

  const dismiss = useCallback(() => {
    setToasts([]);
  }, []);

  const context = useMemo(() => ({
    toasts,
    notify,
    dismiss,
  }), [toasts, notify, dismiss]);

  return context;
};

export default useInlineToasts;
@cheton cheton changed the title Add InlineToastContainer and useInlineToasts examples for inline, non-intrusive notifications in modals and drawer scenarios Add InlineToasts and useInlineToasts for inline, non-intrusive notifications in modals and drawer scenarios Sep 28, 2024
@cheton cheton changed the title Add InlineToasts and useInlineToasts for inline, non-intrusive notifications in modals and drawer scenarios Add InlineToasts and useInlineToasts examples for inline, non-intrusive notifications in modals and drawer scenarios Oct 28, 2024
@cheton cheton linked a pull request Oct 29, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant