Skip to content

Commit

Permalink
Merge pull request #51 from pknu-wap/feature/#50/Add-Hooks
Browse files Browse the repository at this point in the history
Feature/#50/add hooks
  • Loading branch information
alstn113 authored Nov 4, 2022
2 parents 4b2cd9b + be94ab4 commit bb8d283
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wap-ui",
"version": "1.1.6",
"version": "1.1.8",
"repository": "https://github.com/pknu-wap/2022_2_WAP_WEB_TEAM1.git",
"author": "neko113 <alstn113@gmail.com>",
"license": "MIT",
Expand Down
21 changes: 7 additions & 14 deletions packages/components/Modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Modal } from './Modal';
import React, { useState } from 'react';
import React from 'react';
import { Button } from '../Button';
import styled from '@emotion/styled';
import useDisclosure from '../../hooks/useDisclosure';

export default {
title: 'Components/Modal',
component: Modal,
} as ComponentMeta<typeof Modal>;

const Template: ComponentStory<typeof Modal> = () => {
const [visible, setVisible] = useState(false);

const openModal = () => {
setVisible(true);
};

const closeModal = () => {
setVisible(false);
};
const { isOpen, onOpen, onClose } = useDisclosure();

return (
<Container>
<Button onClick={openModal}>버튼</Button>
<Modal isOpen={visible} onClose={closeModal}>
<Modal.Header onClose={closeModal}>Header</Modal.Header>
<Button onClick={onOpen}>버튼</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<Modal.Header onClose={onClose}>Header</Modal.Header>
<Modal.Body>
<span>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
Expand All @@ -38,7 +31,7 @@ const Template: ComponentStory<typeof Modal> = () => {
<Button>Body Button</Button>
</Modal.Body>
<Modal.Footer>
<Button onClick={closeModal}>Close</Button>
<Button onClick={onClose}>Close</Button>
</Modal.Footer>
</Modal>
</Container>
Expand Down
21 changes: 7 additions & 14 deletions packages/components/Portal/Portal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import React, { useState } from 'react';
import React from 'react';
import { Button } from '../Button';
import styled from '@emotion/styled';
import { Modal } from '../Modal';
import { Portal, Props } from './Portal';
import useDisclosure from '../../hooks/useDisclosure';

export default {
title: 'Components/Portal',
component: Portal,
} as ComponentMeta<typeof Portal>;

const Template: ComponentStory<typeof Portal> = (args: Props) => {
const [visible, setVisible] = useState(false);

const openModal = () => {
setVisible(true);
};

const closeModal = () => {
setVisible(false);
};
const { isOpen, onOpen, onClose } = useDisclosure();

return (
<>
<Container>
<Button onClick={openModal}>버튼</Button>
<Button onClick={onOpen}>버튼</Button>
<Portal {...args}>
<Modal isOpen={visible} onClose={closeModal}>
<Modal.Header onClose={closeModal}>Header</Modal.Header>
<Modal isOpen={isOpen} onClose={onClose}>
<Modal.Header onClose={onClose}>Header</Modal.Header>
<Modal.Body>
<span>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
Expand All @@ -41,7 +34,7 @@ const Template: ComponentStory<typeof Portal> = (args: Props) => {
<Button>Body Button</Button>
</Modal.Body>
<Modal.Footer>
<Button onClick={closeModal}>Close</Button>
<Button onClick={onClose}>Close</Button>
</Modal.Footer>
</Modal>
</Portal>
Expand Down
5 changes: 5 additions & 0 deletions packages/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as useDidMountEffect } from './useDidMountEffect';
export { default as useDisclosure } from './useDisclosure';
export { default as useThrottle } from './useThrottle';
export { default as useDebounce } from './useDebounce';
export { default as useIntersectionObserver } from './useIntersectionObserver';
39 changes: 39 additions & 0 deletions packages/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect, useState } from 'react';

interface Props<T> {
value: T;
delay?: number;
}

/**
* useDebounce hook은 delay 안에 값 변경 시 값을 유지한다.
* delay 안에 함수가 한번더 실행되면 앞의 작업을 취소하고 delay이
* 지날 때까지 다시 호출 되지 않으면 callback을 실행하는 형식으로 되어있다.
*
* @example
* const [searchText, setSearchText] = useState<string>('');
* const debouncedText = useDebounce<string>({ value: searchText });
* <TextInput
* placeholder="Search"
* type="text"
* value={searchText}
* onChange={(e) => setSearchText(e.target.value)}
* />
*/
const useDebounce = <T>({ value, delay = 500 }: Props<T>): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => {
clearTimeout(handler);
};
}, [value, delay]);

return debouncedValue;
};

export default useDebounce;
32 changes: 32 additions & 0 deletions packages/hooks/useDisclosure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useCallback, useState } from 'react';

interface Props {
defaultIsOpen?: boolean;
}

/**
* {열린상태, 열기, 닫기, 토글} 의 기능을 가진다.
* @example
* const { isOpen, onOpen, onClose, onToggle } = useDisclosure();
*/

const useDisclosure = ({ defaultIsOpen = false }: Props = {}) => {
const [isOpen, setIsOpen] = useState<boolean>(defaultIsOpen);

const onClose = useCallback(() => {
setIsOpen(false);
}, [setIsOpen]);

const onOpen = useCallback(() => {
setIsOpen(true);
}, [setIsOpen]);

const onToggle = useCallback(() => {
const action = isOpen ? onClose : onOpen;
action();
}, [isOpen, onClose, onOpen]);

return { isOpen, onOpen, onClose, onToggle };
};

export default useDisclosure;
43 changes: 43 additions & 0 deletions packages/hooks/useIntersectionObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useRef } from 'react';

interface useIntersectionObserverProps {
onIntersect: () => void;
}
/**
* IntersectionObserver(교차 관찰자 API)는 타겟 엘레멘트와
* 타겟의 부모 혹은 상위 엘레멘트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API이다.
*
* @example
* const loadMore = () => {
if (hasNextPage) fetchNextPage();
};
const targetElement = useIntersectionObserver({ onIntersect: loadMore });
*/
const useIntersectionObserver = ({
onIntersect,
}: useIntersectionObserverProps) => {
const targetElement = useRef(null);

useEffect(() => {
if (!targetElement || !targetElement.current) return;

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => entry.isIntersecting && onIntersect());
},
{
threshold: 0.5,
},
);

observer.observe(targetElement && targetElement.current);

return () => {
observer.disconnect();
};
}, [onIntersect]);

return targetElement;
};

export default useIntersectionObserver;
29 changes: 29 additions & 0 deletions packages/hooks/useThrottle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect, useRef, useState } from 'react';

interface Props<T> {
value: T;
delay?: number;
}

/**
* useThrottle hook은 delay마다 값의 변경이 이루어 진다.
* delay 뒤에 callback이 실행되고 delay이 지나기전 다시 호출될 경우 아직
* delay이 지나지 않았기 때문에 callback을 실행하지 않고 함수를 종료시키는 형태로 되어있다.
*/
const useTrottle = <T>({ value, delay = 500 }: Props<T>) => {
const [throttleValue, setTrottleValue] = useState<T>(value);
const throttling = useRef(false);
useEffect(() => {
if (throttling.current === false) {
setTrottleValue(value);
throttling.current = true;
}
setTimeout(() => {
if (throttling?.current) throttling.current = false;
}, delay);
}, [value, delay]);

return throttleValue;
};

export default useTrottle;
1 change: 1 addition & 0 deletions packages/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './components';
export * from './layouts';
export * from './hooks';

export { WapUIProvider } from './theme/theme-provider';

0 comments on commit bb8d283

Please sign in to comment.