StreetCommunityProgrammer / metaphore

Story as Code. Public Collections of Metaphore our Freestyler accross the world. Gain knowledge with unusual perspective from our Punk members.
https://metaphore.vercel.app
GNU General Public License v3.0
26 stars 5 forks source link

Next.js - Reusable Confirm Dialog #12

Closed mkubdev closed 1 year ago

mkubdev commented 1 year ago

Metaphore Name

Next.js - Reusable Confirm Dialog

Share your metaphore story!

TODO: Add gif here!

TLDR

This is a complete guide on how to create a reusable confirm dialog component for next.js. Here we go!

Prerequisites

Before you begin, ensure that you have node and npm or Yarn installed on your machine. Here is a run-down of the core technologies we will be using.

Setup

Create a next.js application with tailwind pre-configured:

npx create-next-app@latest --ts -e with-tailwindcss my_app
# or
yarn create next-app --typescript --example with-tailwindcss my_app

Components

ConfirmDialog

import { Dialog, Transition } from '@headlessui/react';
import { Fragment, useState } from 'react';

import ButtonCancel from '@/components/ButtonCancel';
import ButtonConfirm from '@/components/ButtonConfirm';

interface Props {
  title: string;
  children: React.ReactNode;
  open: boolean;
  onClose: Function;
  onConfirm: Function;
}
export default function ConfirmDialog(props: Props) {
  const { open, onClose, title, children, onConfirm } = props;
  if (!open) {
    return <></>;
  }

  return (
    <Transition appear show={open} as={Fragment}>
      <Dialog as="div" className="relative z-10" onClose={onClose}>
        <Transition.Child
          as={Fragment}
          enter="ease-out duration-300"
          enterFrom="opacity-0"
          enterTo="opacity-100"
          leave="ease-in duration-200"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <div className="fixed inset-0 bg-black bg-opacity-25" />
        </Transition.Child>

        <div className="fixed inset-0 overflow-y-auto">
          <div className="flex items-center justify-center min-h-full p-4 text-center">
            <Transition.Child
              as={Fragment}
              enter="ease-out duration-300"
              enterFrom="opacity-0 scale-95"
              enterTo="opacity-100 scale-100"
              leave="ease-in duration-200"
              leaveFrom="opacity-100 scale-100"
              leaveTo="opacity-0 scale-95"
            >
              <Dialog.Panel className="w-full max-w-md p-6 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl">
                <Dialog.Title
                  as="h3"
                  className="text-lg font-medium leading-6 text-gray-900"
                >
                  {title}
                </Dialog.Title>
                <div className="mt-2">
                  <p className="text-sm text-gray-500">{children}</p>
                </div>

                <div className="flex justify-between mt-4">
                  <Button
                    onClick={() => onClose()}
                    className="bg-secondary hover:bg-secondary-light"
                  >
                    No
                  </Button>
                  <ButtonConfirm
                    onClick={() => {
                      onClose();
                      onConfirm();
                    }}
                  >
                    Yes
                  </ButtonConfirm>
                </div>
              </Dialog.Panel>
            </Transition.Child>
          </div>
        </div>
      </Dialog>
    </Transition>
  );
}

ButtonCancel

interface Props {
  children: React.ReactNode;
  type?: 'submit' | 'button' | 'reset';
  onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
  className?: string;
}
export default function ButtonCancel(props: Props) {
  const { type = 'button', children, onClick, className = '' } = props;
  return (
    <button
      className={`bg-red-400 hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline ${className}`}
      type={type}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

ButtonConfirm

interface Props {
  children: React.ReactNode;
  type?: 'submit' | 'button' | 'reset';
  onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
  className?: string;
}
export default function ButtonConfirm(props: Props) {
  const { type = 'button', children, onClick, className = '' } = props;
  return (
    <button
      className={`bg-green-400 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline ${className}`}
      type={type}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

How to use the component?

In order to use the <ConfirmDialog /> component, you need to use a boolean state to display it:

import { useState, useCallback } from 'react';

import ConfirmDialog from './ConfirmDialog';

export default function Home() {
  const [confirmOpen, setConfirmOpen] = useState(false);

  // ...

  const handleConfirm = useCallback(() => {
    console.log('confirmed!');
  }, []);

  return (
    <>
      <button
        className="px-4 py-2 font-bold text-white rounded bg-cyan-400 hover:bg-red-700 focus:outline-none focus:shadow-outline"
        onClick={() => setConfirmOpen(true)}
      >
        Open
      </button>
      <ConfirmDialog
        title="Modal title: Put a modal title!"
        open={confirmOpen}
        onClose={() => setConfirmOpen(false)}
        onConfirm={(e) => handleConfirm(e)}
      >
        Are you sure you have completed all answer?
      </ConfirmDialog>
    </>
  );
}

A demo/repos link

No response

github-actions[bot] commented 1 year ago

Hello Punk! It's great having you contribute to this project

Welcome to the community :neckbeard:

If you would like to continue contributing to open source and would like to do it with an awesome inclusive community, you should join our GitHub Organisation - we help and encourage each other to contribute to open source little and often :neckbeard:. Any questions let us know.

darkterminal commented 1 year ago

Noice!!! 🔥🔥🔥

Using useCallback to more safety. 😁

const handleConfirm = useCallback( () => { 
       console.log('confirmed!'); 
}, []);
mkubdev commented 1 year ago

Noice!!! 🔥🔥🔥

Using useCallback to more safety. 😁

const handleConfirm = useCallback( () => { 
       console.log('confirmed!'); 
}, []);

I will add this change, can you add a suggestion so that i commit change directly? Will finish it today, hehe i forget this one.

darkterminal commented 1 year ago

Is this metaphor are ready to close?

mkubdev commented 1 year ago

Is this metaphor are ready to close?

No, let me complete it! 😁

darkterminal commented 1 year ago

Complete and close this metaphor :100: