SEED Design

Bottom Sheet

화면 하단에서 올라오는 모달 컴포넌트입니다. 추가 정보나 액션 목록을 제공하면서도 현재 컨텍스트를 유지할 때 사용됩니다.

import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetPreview = () => {
  return (
    <BottomSheetRoot>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent title="제목" description="설명을 작성할 수 있어요">
        {/* If you need to omit padding, pass px={0}. */}
        <BottomSheetBody>Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetPreview;

Stackflow

Stackflow와 Bottom Sheet를 함께 사용하는 방법에 대해 알아보세요.

Installation

npx @seed-design/cli@latest add ui:bottom-sheet

Props

BottomSheetRoot

Prop

Type

BottomSheetTrigger

Prop

Type

BottomSheetContent

Prop

Type

BottomSheetBody

Prop

Type

BottomSheetFooter

Prop

Type

Examples

Trigger

<BottomSheetTrigger>asChild 패턴을 사용해 자식 요소가 BottomSheet를 열 수 있도록 합니다.

<BottomSheetTrigger>aria-haspopup="dialog" 속성을 설정하고, BottomSheetopen 상태에 따라 aria-expanded 속성을 자동으로 설정합니다. 이 속성은 스크린 리더와 같은 보조 기술에 유용합니다.

import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetTriggerExample = () => {
  return (
    <BottomSheetRoot>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent title="제목">
        <BottomSheetBody minHeight="x16">Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetTriggerExample;

Controlled

Trigger 외의 방식으로 BottomSheet를 열고 닫을 수 있습니다. 이 경우 open prop을 사용하여 BottomSheet의 상태를 제어합니다.

import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
} from "seed-design/ui/bottom-sheet";

const BottomSheetControlled = () => {
  const [open, setOpen] = useState(false);

  const scheduleOpen = () => {
    setTimeout(() => {
      setOpen(true);
    }, 1000);
  };

  return (
    <>
      <ActionButton onClick={scheduleOpen}>1초 후 열기</ActionButton>
      <BottomSheetRoot open={open} onOpenChange={setOpen}>
        <BottomSheetContent title="제목" description="설명을 작성할 수 있어요">
          {/* If you need to omit padding, pass px={0}. */}
          <BottomSheetBody minHeight="x16">Content</BottomSheetBody>
          <BottomSheetFooter>
            <ActionButton variant="neutralSolid">확인</ActionButton>
          </BottomSheetFooter>
        </BottomSheetContent>
      </BottomSheetRoot>
    </>
  );
};

export default BottomSheetControlled;

Max Height

<BottomSheetBody>maxHeight prop을 전달하여 BottomSheet의 최대 높이를 설정할 수 있습니다.

import { Box, VStack } from "@seed-design/react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetMaxHeight = () => {
  return (
    <BottomSheetRoot>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent title="제목" description="설명을 작성할 수 있어요">
        {/* If you need to omit padding, pass px={0}. */}
        <BottomSheetBody>
          <VStack maxHeight="300px" overflowY="auto">
            <VStack justifyContent="center" alignItems="center" gap="x4" height="100%">
              <Box width="100%" height="100px" bg="bg.layerBasement" />
              <Box width="100%" height="100px" bg="bg.layerBasement" />
              <Box width="100%" height="100px" bg="bg.layerBasement" />
              <Box width="100%" height="100px" bg="bg.layerBasement" />
            </VStack>
          </VStack>
        </BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetMaxHeight;

Snap Points

snapPoints prop을 사용하여 BottomSheet의 커스텀 스냅 포인트를 설정할 수 있습니다.

import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const snapPoints = ["300px", "500px", 1];

const BottomSheetSnapPoints = () => {
  const [snap, setSnap] = useState<number | string | null>(snapPoints[0]);

  return (
    <BottomSheetRoot snapPoints={snapPoints} activeSnapPoint={snap} setActiveSnapPoint={setSnap}>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent
        title="제목"
        description="설명을 작성할 수 있어요"
        /**
         * snap points를 사용할 때는 handle을 표시해야 합니다.
         */
        showHandle
        /**
         * 높이를 100%로 설정해야 snap points가 제대로 작동합니다.
         */
        style={{ height: "100%", maxHeight: "97%" }}
      >
        <BottomSheetBody>Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetSnapPoints;

Fade From Index

<BottomSheetRoot>fadeFromIndex prop을 전달하여 뒷 배경이 어두워지는 시작 인덱스를 설정할 수 있습니다.

import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const snapPoints = ["300px", "500px", 1];

const BottomSheetFadeFromIndex = () => {
  const [snap, setSnap] = useState<number | string | null>(snapPoints[0]);

  return (
    <BottomSheetRoot
      fadeFromIndex={0}
      snapPoints={snapPoints}
      activeSnapPoint={snap}
      setActiveSnapPoint={setSnap}
    >
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent
        title="제목"
        description="설명을 작성할 수 있어요"
        /**
         * snap points를 사용할 때는 handle을 표시해야 합니다.
         */
        showHandle
        /**
         * 높이를 100%로 설정해야 snap points가 제대로 작동합니다.
         */
        style={{ height: "100%", maxHeight: "97%" }}
      >
        <BottomSheetBody>Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetFadeFromIndex;

Show Handle

<BottomSheetContent>showHandle prop을 전달하여 Handle을 표시할 수 있습니다. 기본 값은 false입니다.

import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetShowHandle = () => {
  const [isSheetOpen, setIsSheetOpen] = useState(false);

  return (
    <BottomSheetRoot open={isSheetOpen} onOpenChange={setIsSheetOpen}>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent title="제목" description="설명을 작성할 수 있어요" showHandle>
        {/* If you need to omit padding, pass px={0}. */}
        <BottomSheetBody minHeight="x16">Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid" onClick={() => setIsSheetOpen(false)}>
            닫기
          </ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetShowHandle;

Show Close Button

<BottomSheetContent>showCloseButton prop을 전달하여 닫기 버튼을 표시할 수 있습니다. 기본 값은 true입니다.

showCloseButton을 false로 설정하면 닫기 버튼이 표시되지 않습니다. 이 경우 유저가 BottomSheet를 닫을 수 있는 방법을 제공해야 합니다.

import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetShowCloseButton = () => {
  const [isSheetOpen, setIsSheetOpen] = useState(false);

  return (
    <BottomSheetRoot open={isSheetOpen} onOpenChange={setIsSheetOpen}>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent
        title="제목"
        description="설명을 작성할 수 있어요"
        showCloseButton={false}
      >
        {/* If you need to omit padding, pass px={0}. */}
        <BottomSheetBody minHeight="x16">Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid" onClick={() => setIsSheetOpen(false)}>
            닫기
          </ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetShowCloseButton;

Dismissible

dismissible prop을 false로 설정하면 closeOnEscape, closeOnInteractOutside, draggable 기능이 비활성화됩니다. 의도적으로 BottomSheet를 닫을 수 없게 하고 싶을 때 사용합니다. 이외에는 유저가 BottomSheet를 닫을 수 있는 방법을 제공해야 합니다.

import { useState } from "react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetDismissible = () => {
  const [open, setOpen] = useState(false);

  return (
    <BottomSheetRoot open={open} onOpenChange={setOpen} dismissible={false}>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent title="제목" description="설명을 작성할 수 있어요">
        {/* If you need to omit padding, pass px={0}. */}
        <BottomSheetBody>Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid" onClick={() => setOpen(false)}>
            닫기
          </ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetDismissible;

With Scroll Fog

<BottomSheetBody>Scroll Fog를 사용하여 스크롤 힌트 효과를 적용할 수 있습니다.

ScrollFog는 항상 효과를 표시하므로, 충분한 padding을 제공해야 합니다. 권장 padding인 하단 80px, 상단 20px을 유지해야 합니다.

import { Box, ScrollFog, VStack } from "@seed-design/react";
import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetMaxHeight = () => {
  return (
    <BottomSheetRoot>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent title="제목" description="설명을 작성할 수 있어요">
        {/* If you need to omit padding, pass px={0}. */}
        <BottomSheetBody>
          <VStack maxHeight="300px" overflowY="auto">
            <ScrollFog placement={["top", "bottom"]}>
              <VStack
                justifyContent="center"
                alignItems="center"
                gap="x4"
                height="100%"
                pb="80px"
                pt="20px"
              >
                <Box width="100%" height="100px" bg="bg.layerBasement" />
                <Box width="100%" height="100px" bg="bg.layerBasement" />
                <Box width="100%" height="100px" bg="bg.layerBasement" />
                <Box width="100%" height="100px" bg="bg.layerBasement" />
              </VStack>
            </ScrollFog>
          </VStack>
        </BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetMaxHeight;

Bottom Inset

<BottomSheetContent>style prop을 전달하여 아래 여백을 주기 위해 사용합니다.

import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetBottomInset = () => {
  return (
    <BottomSheetRoot>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent
        title="제목"
        description="설명을 작성할 수 있어요"
        // 모바일 브라우저에서 아래 여백을 주기 위해 사용
        style={{ paddingBottom: "var(--seed-safe-area-bottom)" }}
      >
        {/* If you need to omit padding, pass px={0}. */}
        <BottomSheetBody minHeight="x16">Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetBottomInset;

Handle Only

<BottomSheetRoot>handleOnly prop을 제공하는 경우 쓸어서(swipe) Bottom Sheet를 움직일 수 있는 영역이 핸들 부분으로 제한됩니다.

이 옵션은 <BottomSheetContent showHandle={true}>와 함께 사용할 때만 작동합니다.

import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetHandleOnly = () => {
  return (
    <BottomSheetRoot handleOnly>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent showHandle title="제목" description="설명을 작성할 수 있어요">
        <BottomSheetBody>Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetHandleOnly;

Skip Animation

skipAnimation prop을 사용하여 BottomSheet의 enter/exit 애니메이션을 건너뛸 수 있습니다.

import { ActionButton } from "seed-design/ui/action-button";
import {
  BottomSheetBody,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetRoot,
  BottomSheetTrigger,
} from "seed-design/ui/bottom-sheet";

const BottomSheetSkipAnimation = () => {
  return (
    <BottomSheetRoot skipAnimation>
      <BottomSheetTrigger asChild>
        <ActionButton>Open</ActionButton>
      </BottomSheetTrigger>
      <BottomSheetContent title="제목" description="설명을 작성할 수 있어요">
        <BottomSheetBody>Content</BottomSheetBody>
        <BottomSheetFooter>
          <ActionButton variant="neutralSolid">확인</ActionButton>
        </BottomSheetFooter>
      </BottomSheetContent>
    </BottomSheetRoot>
  );
};

export default BottomSheetSkipAnimation;

Last updated on