Skip to content

Commit

Permalink
feat: useOverlay hook 추가 (#103)
Browse files Browse the repository at this point in the history
* feat: useOverlay hook

* 오버레이 설명 추가

* 의미없는 주석 제거

* 오타 수정
  • Loading branch information
evan-moon authored Jul 26, 2021
1 parent fb43f8b commit 305adcb
Show file tree
Hide file tree
Showing 20 changed files with 175 additions and 285 deletions.
9 changes: 3 additions & 6 deletions src/components/LubyconUIKitProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { ReactNode } from 'react';
import { PortalProvider } from 'contexts/Portal';
import { SnackbarProvider } from 'src/contexts/Snackbar';
import { ModalProvider } from 'src/contexts/Modal';
import { OverlayProvider } from 'src/contexts/Overlay';
import { SnackbarProvider } from 'contexts/Snackbar';
import { OverlayProvider } from 'contexts/Overlay';

interface Props {
children: ReactNode;
Expand All @@ -12,9 +11,7 @@ function LubyconUIKitProvider({ children }: Props) {
return (
<PortalProvider>
<OverlayProvider>
<ModalProvider>
<SnackbarProvider>{children}</SnackbarProvider>
</ModalProvider>
<SnackbarProvider>{children}</SnackbarProvider>
</OverlayProvider>
</PortalProvider>
);
Expand Down
86 changes: 0 additions & 86 deletions src/contexts/Modal.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,28 @@ import React, {
} from 'react';
import { useContext } from 'react';
import { Portal } from 'src';
import { generateID } from 'src/utils';

interface OverlayValues {
open: (element: ReactNode) => string;
close: (overlayId: string) => void;
addToArea: (overlayId: string, element: ReactNode) => void;
removeFromArea: (overlayId: string) => void;
}
const OverlayContext = createContext<OverlayValues>({
open: () => '',
close: () => {},
addToArea: () => {},
removeFromArea: () => {},
});

function OverlayProvider({ children }: PropsWithChildren<unknown>) {
const [overlays, setOverlays] = useState(new Map<string, ReactNode>());

const open = useCallback((element: ReactNode) => {
const overlayId = generateID('overlay');
const addToArea = useCallback((overlayId: string, element: ReactNode) => {
setOverlays((state) => {
const newState = new Map(state);
newState.set(overlayId, element);
return newState;
});

return overlayId;
}, []);

const close = useCallback((overlayId: string) => {
const removeFromArea = useCallback((overlayId: string) => {
setOverlays((state) => {
const newState = new Map(state);
newState.delete(overlayId);
Expand All @@ -44,8 +40,8 @@ function OverlayProvider({ children }: PropsWithChildren<unknown>) {

const values = useMemo(
() => ({
open,
close,
addToArea,
removeFromArea,
}),
[open, close]
);
Expand All @@ -62,8 +58,8 @@ function OverlayProvider({ children }: PropsWithChildren<unknown>) {
);
}

function useOverlay() {
function useOverlayArea() {
return useContext(OverlayContext);
}

export { OverlayProvider, useOverlay };
export { OverlayProvider, useOverlayArea };
24 changes: 24 additions & 0 deletions src/contexts/Overlay/StateReacter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { forwardRef, useState, useCallback, useImperativeHandle } from 'react';
import { OverlayController } from './types';

export interface StateReacterControl {
open: () => void;
close: () => void;
}
const StateReacter = forwardRef<any, { controller: OverlayController }>(function StateReacter(
{ controller },
ref
) {
const [isOpen, setOpen] = useState(false);
const open = useCallback(() => setOpen(true), []);
const close = useCallback(() => setOpen(false), []);

useImperativeHandle(ref, () => ({
open,
close,
}));

return controller({ isOpen, close });
});

export default StateReacter;
2 changes: 2 additions & 0 deletions src/contexts/Overlay/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useOverlay } from './useOverlay';
export { OverlayProvider, useOverlayArea } from './OverlayContext';
5 changes: 5 additions & 0 deletions src/contexts/Overlay/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface OverlayControllerProps {
isOpen: boolean;
close: () => void;
}
export type OverlayController = (props: OverlayControllerProps) => JSX.Element;
53 changes: 53 additions & 0 deletions src/contexts/Overlay/useOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useCallback } from 'react';
import { useRef } from 'react';
import { useMemo } from 'react';
import { generateID } from 'src/utils';
import { useOverlayArea } from './OverlayContext';
import StateReacter, { StateReacterControl } from './StateReacter';
import { OverlayController } from './types';

/**
* @example
* function useMyModal () {
* const { open, close, createOverlayElement } = useOverlay();
* useEffect(() => {
* // 오버레이 엘리먼트 등록
* createOverlayElement((isOpen, close) =>
* <Modal show={isOpen} onClose={close} />
* );
* }, []);
*
* return {
* openMyModal: open,
* closeMyModal: close,
* }
* }
*/
export function useOverlay() {
const { addToArea, removeFromArea } = useOverlayArea();
const stateReacterRef = useRef<StateReacterControl>(null);

const overlayId = useMemo(() => generateID('overlay'), []);

const open = useCallback(() => stateReacterRef.current?.open(), []);

const close = useCallback(() => stateReacterRef.current?.close(), []);

const createOverlayElement = useCallback((controller: OverlayController) => {
addToArea(overlayId, <StateReacter ref={stateReacterRef} controller={controller} />);
}, []);

const destroy = useCallback(() => {
removeFromArea(overlayId);
}, []);

return useMemo(
() => ({
open,
close,
createOverlayElement,
destroy,
}),
[]
);
}
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export { default as Snackbar } from './components/Snackbar';
export { default as Input } from './components/Input';
export { default as Accordion } from './components/Accordion';
export { default as Tag } from './components/Tag';
export { default as Modal, ModalHeader, ModalContent, ModalFooter } from './components/Modal';
export { default as Table, TableHead, TableBody, TableRow, TableCell } from './components/Table';
export { Portal } from './contexts/Portal';
export { useSnackbar } from './contexts/Snackbar';
Expand Down
46 changes: 46 additions & 0 deletions src/sass/components/_Modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.lubycon-modal {
&__overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: get-color('gray100');
opacity: 0.5;
z-index: 1000;
}
&__window-wrapper {
position: fixed;
left: 50%;
top: 50%;
z-index: 1001;
}
&__window {
background-color: get-color('gray10');
border-radius: 4px;
box-sizing: border-box;
&--small {
width: 280px;
padding: 16px 20px;
}
&--medium {
width: 400px;
padding: 20px 24px;
}
}
&__title {
color: get-color('gray100');
margin-top: 0;
margin-bottom: 12px;
}
&__content {
color: get-color('gray70');
margin-bottom: 24px;
white-space: pre-wrap;
}
&__footer {
display: flex;
align-items: center;
justify-content: flex-end;
}
}
3 changes: 3 additions & 0 deletions src/sass/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@
@import './Tag';
@import './Table';
@import './Spacing';

// 스토리용
@import './ProgressBar';
@import './Modal';
Loading

0 comments on commit 305adcb

Please sign in to comment.