์๋จ ๋ด์ฉ ์์ฑ ์์
( 2025-01-31 ~ )
2025. 02. 02 ์ถ๊ฐ
โ useControllableState() ๊ณต๋ถ
โ ReactNode, ReactChild, ReactElement ๊ณต๋ถ ํ, ์ฌ์์ฑ ๋ ์์ (์๋ง ์ฝ๋ ๋ํ ๋ณ๊ฒฝ๋ ์์ )
ํฉ์ฑ ์ปดํฌ๋ํธ์๋ ๋ค์๊ณผ ๊ฐ๋ค
1. Header, Body, Footer๋ฅผ ๋ชจ๋ ํธ์ถํ ๊ฒฝ์ฐ
2. Body์ Footer๋ง ํธ์ถํ ๊ฒฝ์ฐ
3. Body๋ง ํธ์ถํ๊ณ Body์ ์คํ์ผ๋ง์ ๋ฎ์ด์์ด ๊ฒฝ์ฐ



์ฌ์ฉ์๊ฐ ํ์ํ ์ปดํฌ๋ํธ๋ง์ ํธ์ถํจ์ ๋ฐ๋ผ ๋ค์ํ ๋ชจ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์๋ค.
๋ํ ๊ฐ ์ปดํฌ๋ํธ์ className(tailwindCSS), style์ props๋ก ๋๊ฒจ์ฃผ์ด ๋์์ธ์ ์์ ํ ์ ์๋ค.
๐ App.tsx (ํ์ํ ๊ฒฝ์ฐ ํผ์ณ์ ๋ณด๊ธฐ)
import { useState } from "react";
import { BottomSheet, BottomSheetBody, BottomSheetContent, BottomSheetFooter, BottomSheetHeader } from "./BottomSheet";
export default function App() {
const [open, setOpen] = useState(false);
const openBottomSheet = () => {
setOpen(true);
}
const closeBottomSheet = () => {
setOpen(false);
}
return (
<div style={{position:'relative', width: "100vw", height: "100vh", display: "flex", flexDirection:'column', justifyContent: "center", alignItems: "center", backgroundColor:"#1e1f21", color:'white' }}>
<button onClick={openBottomSheet}>Open</button>
<BottomSheet open={open}>
<BottomSheetContent>
<BottomSheetHeader>
<div style={{width:'50px', height:'50px', borderRadius:'50%', display:'flex', justifyContent:'center', alignItems:'center', backgroundColor:'#6ac4bc'}}>
<span style={{color:'white', transform:'rotate(55deg) scaleY(-1)', fontWeight:"normal", fontSize:'20px'}}>7</span>
</div>
</BottomSheetHeader>
<BottomSheetBody>
<div style={{textAlign:'center'}}>
<p style={{color:'white'}}>Body Content </p>
<p style={{color:'white'}}>ma.caron_g</p>
<p style={{color:'white'}}>Tistory</p>
</div>
</BottomSheetBody>
<BottomSheetFooter>
<button onClick={closeBottomSheet}
style={{width:'80%',backgroundColor:'#6ac4bc', color:'white', borderRadius:'10px', border:'none', padding:'10px 20px'}}>ํ์ธ</button>
</BottomSheetFooter>
</BottomSheetContent>
</BottomSheet>
</div>
);
}
๋ค์ ์ฝ๋๋ ๋ธ๋ก๊ทธ๋ฅผ ์์ฑํ๊ธฐ ์ํด ๋น ๋ฅด๊ฒ ์์ฑํ ์ฝ๋์ด๋ฏ๋ก, ๋ถ์กฑํ ์ฝ๋์ผ ์ ์์ต๋๋ค.
๋ค๋ง ๊ตฌ์กฐ์ ์ผ๋ก๋ ํฉ์ฑ ์ปดํฌ๋ํธ์ด๋ ์ฐธ๊ณ ํ๋ฉด ๋ฉ๋๋ค.
๐ BottomSheet.tsx
ํ๋จ์์ ์ฌ์ฉ์์๊ฒ ์ ๋ณด๋ฅผ ์๋ฆฌ๋ BottomSheet๋ฅผ ๊ตฌํํ์์ ๋, ๋ค์๊ณผ ๊ฐ๋ค.
โ ๋ฐํ ์ํธ
โ ์ํธ ๋ค์ ํ๋ฉด์ ๊ฐ๋ฆด Overlay
โ ๋ด์ฉ์ ๋ด์ Container
โ ๋ด์ฉ์ Header, Body, Footer
โ Container์ Overlay๋ฅผ ํ ๋ฒ์ ํธ์ถํ ๋ด์ฉ์ ๋ด์ Content
(์ํธ๊ฐ ๋ง์ดํธ๋๋ฉด ๋ท๋ฐฐ๊ฒฝ(Overlay)์ ๊น๊ธฐ ์ํด Content์ ํฉ์ณค์ผ๋, ๋ถํ์ํ ๊ฒฝ์ฐ ๊ฐ๋ฐ ๋ฐฉํฅ์ ๋ฐ๋ผ ๋ถ๋ฆฌํด๋ ์ข๋ค.)
import React, { forwardRef } from 'react';
import { createPortal } from 'react-dom';
type BottomSheetProps = {
open?: boolean;
} & React.ComponentPropsWithoutRef<"div">;
const BottomSheet = forwardRef<HTMLDivElement, BottomSheetProps>(
({ children, className, open = false, ...props }, ref) => {
if (open)
return (
<div ref={ref} className={className} {...props}>
{children}
</div>
);
}
);
BottomSheet.displayName = "BottomSheet";
const BottomSheetPortal = forwardRef<
HTMLDivElement,
{
children: React.ReactNode;
container?: Element | DocumentFragment | null;
}
>(({ children, container }, ref) => {
return createPortal(
<div ref={ref}>{children}</div>,
container ? container : document.body
);
});
BottomSheetPortal.displayName = "BottomSheetPortal";
const BottomSheetOverlay = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
width: "100%",
height: '100%',
position: 'fixed',
top: '0px',
left: '0px',
display: "flex",
flexDirection: 'column',
justifyContent: "center",
alignItems: "center",
backgroundColor: "#000000cc"
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetOverlay.displayName = "BottomSheetOverlay";
type BottomSheetContentProps = {
container?: Element | DocumentFragment | null;
} & React.ComponentPropsWithoutRef<"div">;
const BottomSheetContent = forwardRef<HTMLDivElement, BottomSheetContentProps>(
({ children, container }, ref) => (
<BottomSheetPortal ref={ref} container={container}>
<BottomSheetOverlay>
<BottomSheetContainer>{children}</BottomSheetContainer>
</BottomSheetOverlay>
</BottomSheetPortal>
)
);
BottomSheetContent.displayName = "BottomSheetContent";
const BottomSheetContainer = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'fixed',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
bottom: '0px',
width: '100%',
height: "40%",
background: '#383838',
borderRadius: '2rem 2rem 0px 0px',
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetContainer.displayName = "BottomSheetContainer";
const BottomSheetHeader = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'relative',
top:'30px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height:'fit-content',
textAlign: 'center',
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetHeader.displayName = "BottomSheetHeader";
const BottomSheetBody = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'relative',
top: '30px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetBody.displayName = "BottomSheetBody";
const BottomSheetFooter = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'absolute',
bottom: '10px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%'
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetFooter.displayName = "BottomSheetFooter";
export {
BottomSheet,
BottomSheetContent,
BottomSheetHeader,
BottomSheetBody,
BottomSheetFooter
};
๋ค์๊ณผ ๊ฐ์ด ์งฐ์ ๋, ๊ฐ๋ฐ์๋ ์ปดํฌ๋ํธ ์ฌ์ฉ์, ํ์ํ ๋ด์ฉ๋ง ํธ์ถํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
'Library > React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ React ] ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ๋ถ๋ฆฌ ๊ธฐ์ค์ ์์๋ณด์. (0) | 2025.04.04 |
---|---|
[ React ] ๋น์ ์ด/์ ์ด ์ํ์ ๋ํด ์์๋ณด์. (feat. @radix-ui/useControllableState) (0) | 2025.02.02 |
[ React / UI ] ์ปดํฌ๋ํธ ์ถ์ํ (2) | 2025.01.31 |
[ React / UI ] Headless UI ์ ๋ํด ์์๋ณด์. (6) | 2025.01.31 |
[ React / UI ] Atomic ๋์์ธ ํจํด์ ๋ํด ์์๋ณด์. (4) | 2024.09.21 |
์๋จ ๋ด์ฉ ์์ฑ ์์
( 2025-01-31 ~ )
2025. 02. 02 ์ถ๊ฐ
โ useControllableState() ๊ณต๋ถ
โ ReactNode, ReactChild, ReactElement ๊ณต๋ถ ํ, ์ฌ์์ฑ ๋ ์์ (์๋ง ์ฝ๋ ๋ํ ๋ณ๊ฒฝ๋ ์์ )
ํฉ์ฑ ์ปดํฌ๋ํธ์๋ ๋ค์๊ณผ ๊ฐ๋ค
1. Header, Body, Footer๋ฅผ ๋ชจ๋ ํธ์ถํ ๊ฒฝ์ฐ
2. Body์ Footer๋ง ํธ์ถํ ๊ฒฝ์ฐ
3. Body๋ง ํธ์ถํ๊ณ Body์ ์คํ์ผ๋ง์ ๋ฎ์ด์์ด ๊ฒฝ์ฐ



์ฌ์ฉ์๊ฐ ํ์ํ ์ปดํฌ๋ํธ๋ง์ ํธ์ถํจ์ ๋ฐ๋ผ ๋ค์ํ ๋ชจ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์๋ค.
๋ํ ๊ฐ ์ปดํฌ๋ํธ์ className(tailwindCSS), style์ props๋ก ๋๊ฒจ์ฃผ์ด ๋์์ธ์ ์์ ํ ์ ์๋ค.
๐ App.tsx (ํ์ํ ๊ฒฝ์ฐ ํผ์ณ์ ๋ณด๊ธฐ)
import { useState } from "react";
import { BottomSheet, BottomSheetBody, BottomSheetContent, BottomSheetFooter, BottomSheetHeader } from "./BottomSheet";
export default function App() {
const [open, setOpen] = useState(false);
const openBottomSheet = () => {
setOpen(true);
}
const closeBottomSheet = () => {
setOpen(false);
}
return (
<div style={{position:'relative', width: "100vw", height: "100vh", display: "flex", flexDirection:'column', justifyContent: "center", alignItems: "center", backgroundColor:"#1e1f21", color:'white' }}>
<button onClick={openBottomSheet}>Open</button>
<BottomSheet open={open}>
<BottomSheetContent>
<BottomSheetHeader>
<div style={{width:'50px', height:'50px', borderRadius:'50%', display:'flex', justifyContent:'center', alignItems:'center', backgroundColor:'#6ac4bc'}}>
<span style={{color:'white', transform:'rotate(55deg) scaleY(-1)', fontWeight:"normal", fontSize:'20px'}}>7</span>
</div>
</BottomSheetHeader>
<BottomSheetBody>
<div style={{textAlign:'center'}}>
<p style={{color:'white'}}>Body Content </p>
<p style={{color:'white'}}>ma.caron_g</p>
<p style={{color:'white'}}>Tistory</p>
</div>
</BottomSheetBody>
<BottomSheetFooter>
<button onClick={closeBottomSheet}
style={{width:'80%',backgroundColor:'#6ac4bc', color:'white', borderRadius:'10px', border:'none', padding:'10px 20px'}}>ํ์ธ</button>
</BottomSheetFooter>
</BottomSheetContent>
</BottomSheet>
</div>
);
}
๋ค์ ์ฝ๋๋ ๋ธ๋ก๊ทธ๋ฅผ ์์ฑํ๊ธฐ ์ํด ๋น ๋ฅด๊ฒ ์์ฑํ ์ฝ๋์ด๋ฏ๋ก, ๋ถ์กฑํ ์ฝ๋์ผ ์ ์์ต๋๋ค.
๋ค๋ง ๊ตฌ์กฐ์ ์ผ๋ก๋ ํฉ์ฑ ์ปดํฌ๋ํธ์ด๋ ์ฐธ๊ณ ํ๋ฉด ๋ฉ๋๋ค.
๐ BottomSheet.tsx
ํ๋จ์์ ์ฌ์ฉ์์๊ฒ ์ ๋ณด๋ฅผ ์๋ฆฌ๋ BottomSheet๋ฅผ ๊ตฌํํ์์ ๋, ๋ค์๊ณผ ๊ฐ๋ค.
โ ๋ฐํ ์ํธ
โ ์ํธ ๋ค์ ํ๋ฉด์ ๊ฐ๋ฆด Overlay
โ ๋ด์ฉ์ ๋ด์ Container
โ ๋ด์ฉ์ Header, Body, Footer
โ Container์ Overlay๋ฅผ ํ ๋ฒ์ ํธ์ถํ ๋ด์ฉ์ ๋ด์ Content
(์ํธ๊ฐ ๋ง์ดํธ๋๋ฉด ๋ท๋ฐฐ๊ฒฝ(Overlay)์ ๊น๊ธฐ ์ํด Content์ ํฉ์ณค์ผ๋, ๋ถํ์ํ ๊ฒฝ์ฐ ๊ฐ๋ฐ ๋ฐฉํฅ์ ๋ฐ๋ผ ๋ถ๋ฆฌํด๋ ์ข๋ค.)
import React, { forwardRef } from 'react';
import { createPortal } from 'react-dom';
type BottomSheetProps = {
open?: boolean;
} & React.ComponentPropsWithoutRef<"div">;
const BottomSheet = forwardRef<HTMLDivElement, BottomSheetProps>(
({ children, className, open = false, ...props }, ref) => {
if (open)
return (
<div ref={ref} className={className} {...props}>
{children}
</div>
);
}
);
BottomSheet.displayName = "BottomSheet";
const BottomSheetPortal = forwardRef<
HTMLDivElement,
{
children: React.ReactNode;
container?: Element | DocumentFragment | null;
}
>(({ children, container }, ref) => {
return createPortal(
<div ref={ref}>{children}</div>,
container ? container : document.body
);
});
BottomSheetPortal.displayName = "BottomSheetPortal";
const BottomSheetOverlay = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
width: "100%",
height: '100%',
position: 'fixed',
top: '0px',
left: '0px',
display: "flex",
flexDirection: 'column',
justifyContent: "center",
alignItems: "center",
backgroundColor: "#000000cc"
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetOverlay.displayName = "BottomSheetOverlay";
type BottomSheetContentProps = {
container?: Element | DocumentFragment | null;
} & React.ComponentPropsWithoutRef<"div">;
const BottomSheetContent = forwardRef<HTMLDivElement, BottomSheetContentProps>(
({ children, container }, ref) => (
<BottomSheetPortal ref={ref} container={container}>
<BottomSheetOverlay>
<BottomSheetContainer>{children}</BottomSheetContainer>
</BottomSheetOverlay>
</BottomSheetPortal>
)
);
BottomSheetContent.displayName = "BottomSheetContent";
const BottomSheetContainer = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'fixed',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
bottom: '0px',
width: '100%',
height: "40%",
background: '#383838',
borderRadius: '2rem 2rem 0px 0px',
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetContainer.displayName = "BottomSheetContainer";
const BottomSheetHeader = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'relative',
top:'30px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height:'fit-content',
textAlign: 'center',
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetHeader.displayName = "BottomSheetHeader";
const BottomSheetBody = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'relative',
top: '30px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetBody.displayName = "BottomSheetBody";
const BottomSheetFooter = forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(
({ children, className, ...props }, ref) => {
return (
<div
ref={ref}
className={className}
style={{
position: 'absolute',
bottom: '10px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%'
}}
{...props}
>
{children}
</div>
);
}
);
BottomSheetFooter.displayName = "BottomSheetFooter";
export {
BottomSheet,
BottomSheetContent,
BottomSheetHeader,
BottomSheetBody,
BottomSheetFooter
};
๋ค์๊ณผ ๊ฐ์ด ์งฐ์ ๋, ๊ฐ๋ฐ์๋ ์ปดํฌ๋ํธ ์ฌ์ฉ์, ํ์ํ ๋ด์ฉ๋ง ํธ์ถํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
'Library > React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ React ] ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ๋ถ๋ฆฌ ๊ธฐ์ค์ ์์๋ณด์. (0) | 2025.04.04 |
---|---|
[ React ] ๋น์ ์ด/์ ์ด ์ํ์ ๋ํด ์์๋ณด์. (feat. @radix-ui/useControllableState) (0) | 2025.02.02 |
[ React / UI ] ์ปดํฌ๋ํธ ์ถ์ํ (2) | 2025.01.31 |
[ React / UI ] Headless UI ์ ๋ํด ์์๋ณด์. (6) | 2025.01.31 |
[ React / UI ] Atomic ๋์์ธ ํจํด์ ๋ํด ์์๋ณด์. (4) | 2024.09.21 |