Cookie Consent by Free Privacy Policy Generator ๐Ÿ“Œ Taming the HTML dialog with React and TailwindCSS

๐Ÿ  Team IT Security News

TSecurity.de ist eine Online-Plattform, die sich auf die Bereitstellung von Informationen,alle 15 Minuten neuste Nachrichten, Bildungsressourcen und Dienstleistungen rund um das Thema IT-Sicherheit spezialisiert hat.
Ob es sich um aktuelle Nachrichten, Fachartikel, Blogbeitrรคge, Webinare, Tutorials, oder Tipps & Tricks handelt, TSecurity.de bietet seinen Nutzern einen umfassenden รœberblick รผber die wichtigsten Aspekte der IT-Sicherheit in einer sich stรคndig verรคndernden digitalen Welt.

16.12.2023 - TIP: Wer den Cookie Consent Banner akzeptiert, kann z.B. von Englisch nach Deutsch รผbersetzen, erst Englisch auswรคhlen dann wieder Deutsch!

Google Android Playstore Download Button fรผr Team IT Security



๐Ÿ“š Taming the HTML dialog with React and TailwindCSS


๐Ÿ’ก Newskategorie: Programmierung
๐Ÿ”— Quelle: dev.to

Today we are going to build a modal component using the native HTML <dialog> element with React and TailwindCSS.

Step 1: Wrap the HTML dialog element

The HTML dialog is a bit tricky to work with, therefore is a good practice to create a wrapper that will give us a declarative API to work with.
Let's start by simply wrapping the element and passing all the props to it:

// Modal.tsx    
import { ComponentPropsWithoutRef } from 'react';

export type ModalProps = ComponentPropsWithoutRef<'dialog'>;

export function Modal(props: ModalProps) {
  const { children, open, ...rest } = props;
  return (
    <dialog {...rest}>
      {children}
    </dialog>
  )
}

If you read the documentation of the HTML dialog element you will notice that it has a open attribute. The natural thing would be to use it with React to toggle the visibility of the modal, but it's not that simple.
If you try the following code you will see that once opened the modal can't be closed:

// App.tsx
import { useState } from "react";
import { Modal } from "./Modal";

export function App() {
  const [open, setOpen] = useState(false);

  return (
    <header>
      <button onClick={() => setOpen(true)}>Open<button>
      <Modal open={open}>
        <button onClick={() => setOpen(false)}>Close</button>
      </Modal>
    </header>
  )
}

This is because the open attribute is supposed to be used to read the state of the dialog, not to set it. To open and close the dialog we need to use the showModal and close methods.

const dialog = document.querySelector('dialog');
dialog.showModal();
console.log(dialog.open); // true
dialog.close();
console.log(dialog.open); // false 

Step 2: Make the dialog play nice with React

To sync the state of the dialog with React we can use a useEffect hook that will listen to the changes of the open prop and call the showModal and close methods accordingly.

// Modal.tsx    
import { useEffect, type ComponentPropsWithoutRef } from 'react';

export type ModalProps = ComponentPropsWithoutRef<'dialog'>;

export function Modal(props: ModalProps) {
  const { children, open, ...rest } = props;
  const ref = useRef<HTMLDialogElement>(null); 

  useEffect(() => {
    const dialog = ref.current!;
    if (open) {
      dialog.showModal();
    } else {
      dialog.close();
    }
  }, [open]);

  return (
    <dialog ref={ref} {...rest}>
      {children}
    </dialog>
  )
}

If you try to run again the code from App.tsx, you will see that the modal behaves correctly now. There is still an issue though. If you open the modal and then press <ESC> the modal will close, but the React state will not be updated. As a result, if you click the open button again, the effect won't re-run and the modal won't open. To fix this we need to listen to the close and cancel events of the dialog and update the state accordingly.

// Modal.tsx    
import { useEffect, type ComponentPropsWithoutRef } from 'react';

export type ModalProps = ComponentPropsWithoutRef<'dialog'> & {
  setOpen: () => void;
};

export function Modal(props: ModalProps) {
  const { children, open, setOpen, ...rest } = props;
  const ref = useRef<HTMLDialogElement>(null); 

  useEffect(() => {
    const dialog = ref.current!;
    if (open) {
      dialog.showModal();
    } else {
      dialog.close();
    }
  }, [open]);

  useEffect(() => {
    const dialog = ref.current!;
    const handler = (e: Event) => {
      e.preventDefault();
      setOpen(false);
    };
    dialog.addEventListener("close", handler);
    dialog.addEventListener("cancel", handler);
    return () => {
      dialog.removeEventListener("close", handler);
      dialog.removeEventListener("cancel", handler);
    };
  }, [setOpen]);

  return (
    <dialog ref={ref} {...rest}>
      {children}
    </dialog>
  )
}

With this change now we should have a fully functional modal component.
Let's now use it to build a search modal.

Step 3: Style the modal

Now that the dialog is functional, let's make it pretty with TailwindCSS.
We are going to modify the Modal component to provide a default set of styles that can be extended by the consumers using the className prop.

// import ...
import { twMerge } from 'tailwind-merge';

// export ...

export function Modal(props: ModalProps) {
  const { children, open, setOpen, className, ...rest } = props;
  const ref = useRef<HTMLDialogElement>(null); 

  // useEffect ...
  // useEffect ...

  return (
    <dialog 
      ref={ref} 
      {...rest}
      className={twMerge(
        "[&::backdrop]:bg-black/75 bg-white w-full max-w-lg p-4 shadow-lg",
        className
      )}  
    >
      {children}
    </dialog>
  )
}

Two things to notice here:

  1. We are using the twMerge function from the tailwind-merge package to merge the default styles with the ones provided by the consumer.
  2. We are using the &::backdrop pseudo-element to style the backdrop of the modal. This is a CSS only solution that works in all the browsers that support the HTML dialog element.

Step 4: Animate the modal (optional)

Animating the modal is a bit tricky because when the dialog is closed, the element is given an implicit display: none style. This means that we can't use CSS transitions to animate the modal.

To solve this problem we can start by moving the backdrop and container of the modal inside the dialog element using divs.

// ...
export function Modal(props: ModalProps) {
  // ...
  return (
    <dialog className={twMerge("group", className)}>
      <div className="bg-black/75 fixed inset-0 grid place-content-center group-open:opacity-100 opacity-0 transition-all">
        <div className="bg-white w-full max-w-lg p-4 shadow-lg group-open:scale-100 group-open:opacity-100 opacity-0 scale-75 transition-all">
          {children}
        </div>
      </div>
    </dialog>
  )
}

Now we can use the group and group-open classes to animate the backdrop and the container of the modal.
There is still one issue though. When the modal is closed, the display: none will hide our exit animation.
To solve this we need to modify our code to make sure that the modal is always visible between the opening and closing animations.

Instead of binding the transition to the open attribute of the dialog, we can use a custom data attribute called data-open. By using a separate attribute we can run the entry and exit animations before the dialog state is updated.

In the useEffect hook we set the custom attribute right away, and then we remove it when the dialog is closed. This way we can use the transitionend event to detect when the exit animation is finished and then close the dialog.

// ...
useEffect(() => {
  const dialog = ref.current!;
  if (open) {
    dialog.showModal();
    dialog.dataset.open = "";
  } else {
    delete dialog.dataset.open;
    const handler = () => dialog.close();
    dialog.addEventListener("transitionend", handler, { once: true });
    return () => dialog.removeEventListener("transitionend", handler);
  }
}, [open]);
// ...

In the HTML we can now change the classes to use group-data-[open] instead of group-open.

Note, for the exit animation to work is important to always close it by setting the open prop to false. If you close the dialog using a form with the method="dialog" attribute, the dialog will be closed without triggering the exit animation.

...



๐Ÿ“Œ Taming the HTML dialog with React and TailwindCSS


๐Ÿ“ˆ 73.06 Punkte

๐Ÿ“Œ 'Create-react-tailwindcss ' an npm package to setup react-tailwindcss configuration


๐Ÿ“ˆ 58.02 Punkte

๐Ÿ“Œ Boost Your Tailwindcss Development with Flow Launcher's Tailwindcss Docs Extension


๐Ÿ“ˆ 40.43 Punkte

๐Ÿ“Œ How To Create Dynamic Donut Charts With TailwindCSS And React


๐Ÿ“ˆ 30.8 Punkte

๐Ÿ“Œ How to Build a GitHub Template Repository for Scaffolding with React, Vite, and TailwindCSS


๐Ÿ“ˆ 30.8 Punkte

๐Ÿ“Œ ๐ŸŽ‰๐ŸŽ‰ Build and Deploy a Fullstack Hotel Booking Web App: Next.js 14, React.js, TS, TailwindCSS, Prisma


๐Ÿ“ˆ 30.8 Punkte

๐Ÿ“Œ What config file defines fonts used in global system dialog boxes, such as the system print dialog?


๐Ÿ“ˆ 29.56 Punkte

๐Ÿ“Œ Copy Dialog Lunar Lander: Dialog-Fenster zum Kopieren von Dateien wird zum Mini-Game


๐Ÿ“ˆ 29.56 Punkte

๐Ÿ“Œ How to add a Light/Dark mode button into a React or Next.js app using TailwindCSS


๐Ÿ“ˆ 29.01 Punkte

๐Ÿ“Œ Storybook 7.0 + React.js + TailwindCSS + CSS modules + Typescript setup that #$%& works


๐Ÿ“ˆ 29.01 Punkte

๐Ÿ“Œ How Do You Declare Custom Classes in a Tailwindcss-React-ts project?


๐Ÿ“ˆ 29.01 Punkte

๐Ÿ“Œ Whatโ€™s New in React 19? React Canaries, Actions, and React Compiler


๐Ÿ“ˆ 28.17 Punkte

๐Ÿ“Œ This Week In React #127: Nextra, React-Query, React Documentary, Storybook, Remix, Tamagui, Solito, TC39, Rome...


๐Ÿ“ˆ 26.39 Punkte

๐Ÿ“Œ This Week In React #131: useReducer, Controlled Inputs, Async React, DevTools, React-Query, Storybook, Remix, RN , Expo...


๐Ÿ“ˆ 26.39 Punkte

๐Ÿ“Œ This Week In React #139: React.dev, Remix, Server Components, Error Boundary, Wakuwork, React-Native, Bottom Sheet...


๐Ÿ“ˆ 26.39 Punkte

๐Ÿ“Œ This Week In React #142: React-Query, Million, OpenNext, Ariakit, Expo-Image, React-Three-Fiber, TS 5.1, Node.js 20, WebGPU...


๐Ÿ“ˆ 26.39 Punkte

๐Ÿ“Œ This Week In React #146: Concurrency, Server Components, Next.js, React-Query, Remix, Expo Router, Skia, React-Native...


๐Ÿ“ˆ 26.39 Punkte

๐Ÿ“Œ DEF CON Safe Mode IoT Village - Kat Fitzgerald - IoT Honeypots and Taming Rogue Appliances


๐Ÿ“ˆ 24.65 Punkte

๐Ÿ“Œ Broadcom Software: Taming IT Complexity through Effective Strategies and Partnerships


๐Ÿ“ˆ 24.65 Punkte

๐Ÿ“Œ Taming the Flame: Securely Connecting Next.js and Firebase with TypeScript


๐Ÿ“ˆ 24.65 Punkte

๐Ÿ“Œ The Thaumaturge review: a monster taming RPG with a mature, dark, and moody twist


๐Ÿ“ˆ 24.65 Punkte

๐Ÿ“Œ Debouncing and Throttling: Taming the Wild West of JavaScript Events


๐Ÿ“ˆ 24.65 Punkte

๐Ÿ“Œ [$] Taming STIBP


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ 35C3 - Taming the Chaos: Can we build systems that actually work? - deutsche รœbersetzung


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ 35C3 - Taming the Chaos: Can we build systems that actually work?


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Taming Global Cybersecurity Risks Requires a Concerted Cyber Resilience Effort


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Taming Reactive NodeJS: Stream-oriented Architecture with Nest


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Taming Vulnerability Overload - Mehul Revankar - PSW #688


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Taming the Overprivileged Cloud


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Taming the Digital Asset Tsunami


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ First 72 Hours of Incident Response Critical to Taming Cyberattack Chaos


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Taming multiple design system with a single plugin


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Taming multiple GApp accounts


๐Ÿ“ˆ 22.87 Punkte

๐Ÿ“Œ Backstage.io - An Open Source Portal for Taming Developer Chaos


๐Ÿ“ˆ 22.87 Punkte











matomo