BottomSheet 10.16.0

ARIA
RTL

The bottom sheet component is a modified dialog that slides from the bottom of the screen, common pattern in mobile apps.

Bottom sheets can contain any anything so let your imagination fly. Based on Headless UI.

Anatomy

<BottomSheet>
  <BottomSheet.Panel>
    <BottomSheet.Draghandle>
      <BottomSheet.Title>...</BottomSheet.Title>
    </BottomSheet.Draghandle>
    ...
  </BottomSheet.Panel>
  <BottomSheet.Backdrop />
</BottomSheet>

Default

By default, the <Bottomsheet.Panel /> occupies a height of 33.33% (h-1/3) of the viewport.

"use client";

import { useState } from "react";
import { BottomSheet, Button } from "@heathmont/moon-core-tw";

export const Default = () => {
  const [isOpen, setIsOpen] = useState(false);
  const closeBottomSheet = () => {
    setIsOpen(false);
  };
  const openBottomSheet = () => {
    setIsOpen(true);
  };
  return (
    <>
      <Button variant="outline" onClick={openBottomSheet}>
        Default BottomSheet
      </Button>
      <BottomSheet open={isOpen} onClose={closeBottomSheet}>
        <BottomSheet.Panel>
          <div className="flex grow items-center justify-center bg-jiren text-piccolo">
            BottomSheet content
          </div>
        </BottomSheet.Panel>
        <BottomSheet.Backdrop />
      </BottomSheet>
    </>
  );
};

export default Default;

    Sizes

    Adjust the default height of the bottom sheet panel by modifying the CSS height class of <BottomSheet.Panel>

    "use client";
    
    import { useState } from "react";
    import { BottomSheet, Button } from "@heathmont/moon-core-tw";
    
    export const Sizes = () => {
      const [isSmallOpen, setIsSmallOpen] = useState(false);
      const closeSmallBottomSheet = () => {
        setIsSmallOpen(false);
      };
      const openSmallBottomSheet = () => {
        setIsSmallOpen(true);
      };
      const [isMediumOpen, setIsMediumOpen] = useState(false);
      const closeMediumBottomSheet = () => {
        setIsMediumOpen(false);
      };
      const openMediumBottomSheet = () => {
        setIsMediumOpen(true);
      };
      const [isFullOpen, setIsFullOpen] = useState(false);
      const closeFullBottomSheet = () => {
        setIsFullOpen(false);
      };
      const openFullBottomSheet = () => {
        setIsFullOpen(true);
      };
      return (
        <>
          <div>
            <Button variant="outline" onClick={openSmallBottomSheet}>
              Default small BottomSheet
            </Button>
            <BottomSheet open={isSmallOpen} onClose={closeSmallBottomSheet}>
              <BottomSheet.Panel className="flex flex-col gap-2 items-center">
                <div className="flex grow w-full items-center justify-center bg-jiren text-piccolo">
                  BottomSheet content
                </div>
                <Button onClick={closeSmallBottomSheet}>Optional close</Button>
              </BottomSheet.Panel>
              <BottomSheet.Backdrop />
            </BottomSheet>
          </div>
          <div>
            <Button variant="outline" onClick={openMediumBottomSheet}>
              Medium BottomSheet
            </Button>
            <BottomSheet open={isMediumOpen} onClose={closeMediumBottomSheet}>
              <BottomSheet.Panel className="flex flex-col gap-2 items-center h-2/3">
                <div className="flex grow w-full items-center justify-center bg-jiren text-piccolo">
                  BottomSheet content
                </div>
                <Button onClick={closeMediumBottomSheet}>Optional close</Button>
              </BottomSheet.Panel>
              <BottomSheet.Backdrop />
            </BottomSheet>
          </div>
          <div>
            <Button variant="outline" onClick={openFullBottomSheet}>
              Fullscreen BottomSheet
            </Button>
            <BottomSheet open={isFullOpen} onClose={closeFullBottomSheet}>
              <BottomSheet.Panel className="flex flex-col gap-2 items-center h-full">
                <div className="flex grow w-full items-center justify-center bg-jiren text-piccolo">
                  BottomSheet content
                </div>
                <Button onClick={closeFullBottomSheet}>Optional close</Button>
              </BottomSheet.Panel>
              <BottomSheet.Backdrop />
            </BottomSheet>
          </div>
        </>
      );
    };
    
    export default Sizes;
    

      With draghandle

      Enhance the bottom sheet's usability on touchscreen devices by incorporating a drag handle, which enables swipe functionality.

      "use client";
      
      import { useState } from "react";
      import { BottomSheet, Button } from "@heathmont/moon-core-tw";
      
      export const WithDraghandle = () => {
        const [isOpen, setIsOpen] = useState(false);
        const closeBottomSheet = () => {
          setIsOpen(false);
        };
        const openBottomSheet = () => {
          setIsOpen(true);
        };
        return (
          <>
            <Button variant="outline" onClick={openBottomSheet}>
              BottomSheet with Draghandle
            </Button>
            <BottomSheet open={isOpen} onClose={closeBottomSheet}>
              <BottomSheet.Panel>
                <BottomSheet.Draghandle />
                <div className="flex grow items-center justify-center bg-jiren text-piccolo">
                  BottomSheet content
                </div>
              </BottomSheet.Panel>
              <BottomSheet.Backdrop />
            </BottomSheet>
          </>
        );
      };
      
      export default WithDraghandle;
      

        With title

        To add a title to your bottom sheet, insert the <BottomSheet.Title /> sub-component inside <Bottomsheet.Panel />.

        "use client";
        
        import { useState } from "react";
        import { BottomSheet, Button } from "@heathmont/moon-core-tw";
        
        export const WithTitle = () => {
          const [isTitleOpen, setIsTitleOpen] = useState(false);
          const closeTitleBottomSheet = () => {
            setIsTitleOpen(false);
          };
          const openTitleBottomSheet = () => {
            setIsTitleOpen(true);
          };
          const [isMediumOpen, setIsMediumOpen] = useState(false);
          const closeMediumBottomSheet = () => {
            setIsMediumOpen(false);
          };
          const openMediumBottomSheet = () => {
            setIsMediumOpen(true);
          };
          return (
            <>
              <div>
                <Button variant="outline" onClick={openTitleBottomSheet}>
                  BottomSheet with Title
                </Button>
                <BottomSheet open={isTitleOpen} onClose={closeTitleBottomSheet}>
                  <BottomSheet.Panel>
                    <BottomSheet.Title>BottomSheet Title</BottomSheet.Title>
                    <div className="flex grow items-center justify-center bg-jiren text-piccolo">
                      BottomSheet content
                    </div>
                  </BottomSheet.Panel>
                  <BottomSheet.Backdrop />
                </BottomSheet>
              </div>
              <div>
                <Button variant="outline" onClick={openMediumBottomSheet}>
                  BottomSheet with Title and Draghandle
                </Button>
                <BottomSheet open={isMediumOpen} onClose={closeMediumBottomSheet}>
                  <BottomSheet.Panel>
                    <BottomSheet.Draghandle>
                      <BottomSheet.Title>BottomSheet Title</BottomSheet.Title>
                    </BottomSheet.Draghandle>
                    <div className="flex grow items-center justify-center bg-jiren text-piccolo">
                      BottomSheet content
                    </div>
                  </BottomSheet.Panel>
                  <BottomSheet.Backdrop />
                </BottomSheet>
              </div>
            </>
          );
        };
        
        export default WithTitle;
        

          Customization

          In case you need to add some extra styling to the <BottomSheet /> component, you can easily use the className property. It applies not only to the <BottomSheet /> component itself but also to all its sub-components - <BottomSheet.Panel />, <BottomSheet.Draghandle />, <BottomSheet.Title />, and <BottomSheet.Backdrop />.

          "use client";
          
          import { useState } from "react";
          import { BottomSheet, Button } from "@heathmont/moon-core-tw";
          
          export const Customization = () => {
            const [isOpen, setIsOpen] = useState(false);
            const closeBottomSheet = () => {
              setIsOpen(false);
            };
            const openBottomSheet = () => {
              setIsOpen(true);
            };
            return (
              <>
                <Button variant="outline" onClick={openBottomSheet}>
                  Customized BottomSheet
                </Button>
                <BottomSheet open={isOpen} onClose={closeBottomSheet}>
                  <BottomSheet.Panel className="bg-raditz p-4">
                    <BottomSheet.Draghandle className="after:bg-piccolo">
                      <BottomSheet.Title className="pt-2 text-goten">
                        BottomSheet Title
                      </BottomSheet.Title>
                    </BottomSheet.Draghandle>
                    <div className="flex grow items-center justify-center bg-jiren text-piccolo">
                      BottomSheet content
                    </div>
                  </BottomSheet.Panel>
                  <BottomSheet.Backdrop />
                </BottomSheet>
              </>
            );
          };
          
          export default Customization;
          

            Custom root element

            By default, BottomSheet content is inserted as a portal directly into the document.body. If it's necessary to insert a component into a custom DOM node, follow these steps:


            1. Add the following HTML code in the location where the component should be rendered:
              <div id="headlessui-portal-root"><div /></div>
            2. Set a prop rootId to the <BottomSheet> component with the id of the root element. Example:
              <BottomSheet rootId="__next">...</BottomSheet>
              By doing this, the "inert" attribute that was added to an ancestor will be removed automatically, and the BottomSheet will become interactive.
            "use client";
            
            import { useState } from "react";
            import { BottomSheet, Button } from "@heathmont/moon-core-tw";
            
            const addElement = () => {
              const portalRootDiv = document.createElement("div");
              portalRootDiv.id = "headlessui-portal-root";
              const childrenDiv = document.createElement("div");
              portalRootDiv.appendChild(childrenDiv);
              const currentDiv = document.getElementById("root-element");
              currentDiv?.appendChild(portalRootDiv);
            };
            
            const deleteElement = () => {
              const portalRootDiv = document.getElementById("headlessui-portal-root");
              portalRootDiv?.remove();
            };
            
            export const RootPortal = () => {
              const [isOpen, setIsOpen] = useState(false);
              const closeBottomSheet = () => {
                setIsOpen(false);
                setTimeout(() => {
                  deleteElement();
                }, 300);
              };
              const openBottomSheet = () => {
                addElement();
                setIsOpen(true);
              };
              return (
                <div id="root-element">
                  <Button variant="outline" onClick={openBottomSheet}>
                    BottomSheet
                  </Button>
                  <BottomSheet open={isOpen} rootId="__next">
                    <BottomSheet.Panel onClose={closeBottomSheet}>
                      <BottomSheet.Draghandle />
                      <div className="flex grow items-center justify-center bg-jiren text-piccolo">
                        BottomSheet content
                      </div>
                    </BottomSheet.Panel>
                    <BottomSheet.Backdrop onClose={closeBottomSheet} />
                  </BottomSheet>
                </div>
              );
            };
            
            export default RootPortal;
            

              BottomSheet

              These are props specific to the BottomSheet component:

              Name
              Type
              Default
              onClose*
              "() => void"-
              open*
              boolean-
              rootId
              string-
              className
              string-

              Properties indicated with * are required.

              BottomSheet.Panel

              These are props specific to the BottomSheet.Panel component:

              Name
              Type
              Default
              className
              string-
              onClose
              "() => void"-

              BottomSheet.Title

              These are props specific to the BottomSheet.Title component:

              Name
              Type
              Default
              className
              string-

              BottomSheet.Draghandle

              These are props specific to the BottomSheet.Draghandle component:

              Name
              Type
              Default
              className
              string-

              BottomSheet.Backdrop

              These are props specific to the BottomSheet.Backdrop component:

              Name
              Type
              Default
              className
              string-