Grunfeld
ํน์ง
- ๐ ๊ฐ๋จํ API: ๋ณต์กํ ์ํ ๊ด๋ฆฌ ์์ด ๋ช ์ค์ ์ฝ๋๋ก ๋ํ์์๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค
- ๐ฏ ๋๊ธฐ/๋น๋๊ธฐ ์ง์: ์ผ๋ฐ์ ์ธ ์๋ฆผ๋ถํฐ ์ฌ์ฉ์ ์๋ต์ด ํ์ํ ํ์ธ ๋ํ์์๊น์ง ๋ชจ๋ ์๋๋ฆฌ์ค๋ฅผ ์ง์ํฉ๋๋ค
- ๐ฑ ์ ์ฐํ ์์น ์ค์ : ์ค์ ๋ชจ๋ฌ, ํ๋จ ์ํธ ๋ฑ ๋ค์ํ UI ํจํด์ ๋ง์ถฐ ์์น๋ฅผ ์กฐ์ ํ ์ ์์ต๋๋ค
- ๐ ์ค๋งํธํ ์คํ ๊ด๋ฆฌ: ์ฌ๋ฌ ๋ํ์์๊ฐ ์ด๋ฆด ๋ ๋ ผ๋ฆฌ์ ์ธ LIFO(Last In First Out) ์์๋ก ๊ด๋ฆฌ๋ฉ๋๋ค
- ๐จ ์ปค์คํ ์คํ์ผ๋ง: ๋ฐฑ๋๋กญ ์คํ์ผ๋ถํฐ ๊ฐ๋ณ ๋ํ์์ ์คํ์ผ๊น์ง ์์ ๋กญ๊ฒ ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅํฉ๋๋ค
- ๐ ์ง๊ด์ ์ธ UX: ๋ฐฐ๊ฒฝ ํด๋ฆญ์ผ๋ก ๋ซ๊ธฐ, ์๋ ํฌ์ปค์ค ๊ด๋ฆฌ ๋ฑ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ณ ๋ คํ ๊ธฐ๋ฅ๋ค์ ์ ๊ณตํฉ๋๋ค๋ React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ๊ฐ๋จํ๊ณ ๊ฐ๋ฒผ์ด ๋ํ์์(dialog) ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
์ค์น
npm install grunfeld
# ๋๋
yarn add grunfeld
ํน์ง
- ๐ ๊ฐ๋จํ API๋ก ๋ํ์์ ๊ด๋ฆฌ
- ๏ฟฝ ๋๊ธฐ/๋น๋๊ธฐ ๋ํ์์ ์ง์
- ๐ฑ ์์น ์ค์ ๊ฐ๋ฅ ('center' ๋๋ 'bottom')
- ๐ ๋ค์ค ๋ํ์์ ์คํ ์ง์
- ๐จ ์ปค์คํ ์คํ์ผ๋ง ์ง์
- ๐ Light dismiss (๋ฐฐ๊ฒฝ ํด๋ฆญ์ผ๋ก ๋ซ๊ธฐ) ์ต์
์ฌ์ฉ๋ฒ
๊ธฐ๋ณธ ์ค์
์ ํ๋ฆฌ์ผ์ด์
์ ์ต์์ ๋ ๋ฒจ์ GrunfeldProvider
๋ฅผ ์ถ๊ฐํ์ธ์. ์ด ์ปดํฌ๋ํธ๋ ๋ชจ๋ ๋ํ์์๋ฅผ ๋ ๋๋งํ๊ณ ๊ด๋ฆฌํ๋ ์ปจํ
์คํธ๋ฅผ ์ ๊ณตํฉ๋๋ค:
import { GrunfeldProvider } from "grunfeld";
function App() {
return (
<GrunfeldProvider
options={{
defaultPosition: "center",
defaultDismiss: true,
backdropStyle: {
/* ์ปค์คํ
๋ฐฑ๋๋กญ ์คํ์ผ */
},
}}
>
{/* ์ ํ๋ฆฌ์ผ์ด์
๋ด์ฉ */}
</GrunfeldProvider>
);
}
๊ธฐ๋ณธ ๋ํ์์ ํ์
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ์ ๋ํ์์์ ๋๋ค. ์ฌ์ฉ์์๊ฒ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ฃผ๊ฑฐ๋ ๊ฐ๋จํ ์ํธ์์ฉ์ด ํ์ํ ๋ ์ฌ์ฉํฉ๋๋ค:
import { grunfeld } from "grunfeld";
function YourComponent() {
const showDialog = () => {
grunfeld.add({
element: <div>์๋
ํ์ธ์!</div>,
position: "center",
lightDismiss: true,
});
};
return <button onClick={showDialog}>๋ํ์์ ์ด๊ธฐ</button>;
}
๋น๋๊ธฐ ๋ํ์์ (์ฌ์ฉ์ ์๋ต ๋๊ธฐ)
์ฌ์ฉ์์ ์ ํ์ด๋ ์ ๋ ฅ์ ๊ธฐ๋ค๋ ค์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค. Promise๋ฅผ ๋ฐํํ๋ฏ๋ก async/await ํจํด์ผ๋ก ์ฌ์ฉ์์ ์๋ต์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค:
import { grunfeld } from "grunfeld";
function YourComponent() {
const showConfirmDialog = async () => {
const result = await grunfeld.addAsync((removeWith) => ({
element: (
<div>
<p>์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?</p>
<button onClick={() => removeWith(true)}>ํ์ธ</button>
<button onClick={() => removeWith(false)}>์ทจ์</button>
</div>
),
position: "center",
}));
if (result) {
console.log("์ฌ์ฉ์๊ฐ ํ์ธ์ ํด๋ฆญํ์ต๋๋ค");
} else {
console.log("์ฌ์ฉ์๊ฐ ์ทจ์๋ฅผ ํด๋ฆญํ์ต๋๋ค");
}
};
return <button onClick={showConfirmDialog}>ํ์ธ ๋ํ์์</button>;
}
๊ฐ๋จํ ๋ํ์์ (ReactNode๋ง ์ ๋ฌ)
๋ณต์กํ ์ค์ ์์ด JSX ์์๋ ๋ฌธ์์ด์ ์ง์ ์ ๋ฌํ์ฌ ๋น ๋ฅด๊ฒ ๋ํ์์๋ฅผ ํ์ํ ์ ์์ต๋๋ค:
import { grunfeld } from "grunfeld";
function YourComponent() {
const showSimpleDialog = () => {
// ReactNode๋ฅผ ์ง์ ์ ๋ฌํ ์ ์์ต๋๋ค
grunfeld.add(<div>๊ฐ๋จํ ๋ฉ์์ง</div>);
};
return <button onClick={showSimpleDialog}>๊ฐ๋จํ ๋ํ์์</button>;
}
๋ํ์์ ์ ๊ฑฐ
Grunfeld๋ ๋ํ์์๋ค์ ์คํ(Stack) ๊ตฌ์กฐ๋ก ๊ด๋ฆฌํฉ๋๋ค. ์ด๋ ๋ํ์์๋ค ๊ฐ์ ๋งฅ๋ฝ์ ๊ด๊ณ๋ฅผ ์ ์งํ๊ธฐ ์ํจ์ ๋๋ค.
์๋ฅผ ๋ค์ด, A ๋ํ์์์์ B ๋ํ์์๋ฅผ ์ด์๋ค๋ฉด, B๊ฐ A์ ๊ฒฐ๊ณผ๋ก ์์ฑ๋ ๊ฒ์ด๋ฏ๋ก B๊ฐ ๋จผ์ ๋ซํ์ผ ๋
ผ๋ฆฌ์ ์ผ๋ก ์ฌ๋ฐ๋ฆ
๋๋ค. ์ด๋ฌํ ์์น์ ๋ฐ๋ผ remove()
๋ ํญ์ ๊ฐ์ฅ ์ต๊ทผ์ ์ด๋ฆฐ ๋ํ์์๋ถํฐ ์ ๊ฑฐํฉ๋๋ค.
import { grunfeld } from "grunfeld";
// ๊ฐ์ฅ ์ต๊ทผ ๋ํ์์ ์ ๊ฑฐ
grunfeld.remove();
// ๋ชจ๋ ๋ํ์์ ์ ๊ฑฐ
grunfeld.clear();
API ์ฐธ์กฐ
GrunfeldProvider
์์ฑ | ํ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
---|---|---|---|
children | ReactNode | ํ์ | ์์ ์ปดํฌ๋ํธ |
options | GrunfeldProviderOptions | - | ๊ธฐ๋ณธ ์ค์ ์ต์ |
GrunfeldProviderOptions
์์ฑ | ํ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช | |
---|---|---|---|---|
defaultPosition | 'center' \ | 'bottom' | 'center' | ๋ํ์์์ ๊ธฐ๋ณธ ์์น |
defaultDismiss | boolean | true | ๊ธฐ๋ณธ light dismiss ์ค์ | |
backdropStyle | CSSProperties | - | ๋ฐฑ๋๋กญ ์ปค์คํ ์คํ์ผ |
grunfeld ๊ฐ์ฒด
grunfeld.add(dialog)
์๋ก์ด ๋ํ์์๋ฅผ ์ถ๊ฐํฉ๋๋ค.
๋งค๊ฐ๋ณ์:
dialog: GrunfeldProps
- ๋ํ์์ ์ค์
GrunfeldProps:
type GrunfeldProps =
| {
element: React.ReactNode;
position?: "center" | "bottom";
lightDismiss?: boolean;
dismissCallback?: () => unknown;
}
| React.ReactNode;
grunfeld.addAsync<T>(dialog)
๋น๋๊ธฐ ๋ํ์์๋ฅผ ์ถ๊ฐํ๊ณ ์ฌ์ฉ์ ์๋ต์ ๊ธฐ๋ค๋ฆฝ๋๋ค.
๋งค๊ฐ๋ณ์:
dialog: (removeWith: (data: T) => T) => GrunfeldProps
- ๋ํ์์ ํฉํ ๋ฆฌ ํจ์
๋ฐํ๊ฐ:
Promise<T>
- ์ฌ์ฉ์๊ฐremoveWith
๋ฅผ ํธ์ถํ ๋ ์ ๋ฌํ ๋ฐ์ดํฐ
grunfeld.remove()
๊ฐ์ฅ ์ต๊ทผ์ ์ถ๊ฐ๋ ๋ํ์์๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
์ด ๋ฉ์๋๋ LIFO(Last In First Out) ์์น์ ๋ฐ๋ฆ ๋๋ค. ์ฌ๋ฌ ๋ํ์์๊ฐ ์ด๋ ค์์ ๋, ๊ฐ์ฅ ๋ง์ง๋ง์ ์ด๋ฆฐ ๋ํ์์๋ถํฐ ์์๋๋ก ๋ซํ๋๋ค. ์ด๋ ๋ํ์์๋ค ๊ฐ์ ๋งฅ๋ฝ์ ๊ด๊ณ๋ฅผ ์ ์งํ๊ธฐ ์ํ ์ค๊ณ์ ๋๋ค.
// ์์: A โ B โ C ์์๋ก ์ด๋ฆฐ ๊ฒฝ์ฐ
grunfeld.remove(); // C๊ฐ ๋ซํ
grunfeld.remove(); // B๊ฐ ๋ซํ
grunfeld.remove(); // A๊ฐ ๋ซํ
grunfeld.clear()
๋ชจ๋ ๋ํ์์๋ฅผ ํ ๋ฒ์ ์ ๊ฑฐํฉ๋๋ค.
๊ธด๊ธํ ์ํฉ์ด๋ ํ์ด์ง ์ ํ ์ ๋ชจ๋ ๋ํ์์๋ฅผ ์ ๋ฆฌํด์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค. ๊ฐ ๋ํ์์์ dismissCallback
์ด ์๋ค๋ฉด ๋ชจ๋ ์คํ๋ ํ ์ ๊ฑฐ๋ฉ๋๋ค.
๊ณ ๊ธ ์ฌ์ฉ๋ฒ
์ปค์คํ dismiss ์ฝ๋ฐฑ
๋ํ์์๊ฐ ๋ซํ ๋ ํน์ ๋ก์ง์ ์คํํด์ผ ํ๋ ๊ฒฝ์ฐ dismissCallback
์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ ์ ๋ฆฌ ์์
, ์ํ ์
๋ฐ์ดํธ, ๋ถ์ ์ด๋ฒคํธ ์ ์ก ๋ฑ์ ์ ์ฉํฉ๋๋ค:
grunfeld.add({
element: <MyDialog />,
dismissCallback: () => {
console.log("๋ํ์์๊ฐ ๋ซํ์ต๋๋ค");
// ์ ๋ฆฌ ์์
์ํ
},
});
์์น๋ณ ๋ํ์์
๋ค์ํ UI ํจํด์ ๋ง์ถฐ ๋ํ์์์ ์์น๋ฅผ ์กฐ์ ํ ์ ์์ต๋๋ค. ๊ฐ ์์น๋ ์๋ก ๋ค๋ฅธ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค:
// ์ค์์ ํ์
grunfeld.add({
element: <CenterDialog />,
position: "center",
});
// ํ๋จ์ ํ์ (๋ฐํ
์ํธ ์คํ์ผ)
grunfeld.add({
element: <BottomSheet />,
position: "bottom",
});