Skip to content

Commit

Permalink
feat(ui-kit): Accordion 컴포넌트 추가 (#47)
Browse files Browse the repository at this point in the history
* feat(ui-kit): Accordion 컴포넌트 추가

* feat(ui-kit): 아코디언 라벨 스타일 수정

* feat(ui-kit): input 컴포넌트 가현님 피드백 적용

* feat(ui-kit): 아코디언 상태 변경 이벤트 추가
  • Loading branch information
evan-moon committed Feb 19, 2021
1 parent 766105f commit dbdd25b
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 3 deletions.
3 changes: 2 additions & 1 deletion ui-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"dependencies": {
"classnames": "^2.2.6",
"ionicons": "^5.2.3",
"react-spring": "^8.0.27"
"react-spring": "^8.0.27",
"resize-observer-polyfill": "^1.5.1"
},
"scripts": {
"start": "start-storybook -p 6006 --no-dll",
Expand Down
86 changes: 86 additions & 0 deletions ui-kit/src/components/Accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { forwardRef, HTMLAttributes, useEffect, useRef, useState } from 'react';
import { Combine } from 'src/types/utils';
import classnames from 'classnames';
import Icon from '../Icon';
import { chevronDown } from 'ionicons/icons';
import Text from '../Text';
import { useResizeObserver } from 'src/hooks/useResizeObserver';
import { colors } from 'src/constants/colors';

type Props = Combine<
{
label: string;
defaultOpen?: boolean;
onChange?: (state: boolean) => void;
onOpen?: () => void;
onClose?: () => void;
},
HTMLAttributes<HTMLDivElement>
>;
const Accordion = forwardRef<HTMLDivElement, Props>(function Accordion(
{ label, className, children, defaultOpen = false, onChange, onOpen, onClose, ...props },
ref
) {
const [open, setOpen] = useState(defaultOpen);
const contentRef = useRef<HTMLDivElement>(null);
const [bodyHeight, setBodyHeight] = useState(0);

const toggleContentOpen = () => {
setOpen((state) => !state);
};

const updateContentHeight = () =>
setBodyHeight(contentRef.current?.getBoundingClientRect().height ?? 0);

useResizeObserver(contentRef, updateContentHeight);

useEffect(() => {
onChange?.(open);
}, [open]);

useEffect(() => {
if (open === true) {
onOpen?.();
} else {
onClose?.();
}
}, [open]);

return (
<div
ref={ref}
className={classnames(
'lubycon-accordion',
{
'lubycon-accordion--opened': open,
},
className
)}
{...props}
>
<div className="lubycon-accordion__label" onClick={toggleContentOpen} role="button">
<Icon
icon={chevronDown}
type="outline"
size={20}
className="lubycon-accordion__label__icon"
color={colors.gray90}
/>
<Text typography="subtitle" className="lubycon-accordion__label__text">
{label}
</Text>
</div>
<div className="lubycon-accordion__cover" style={{ height: open ? bodyHeight : 0 }}>
<div
className="lubycon-accordion__cover__content"
style={{ opacity: open ? 1 : 0 }}
ref={contentRef}
>
{children}
</div>
</div>
</div>
);
});

export default Accordion;
23 changes: 23 additions & 0 deletions ui-kit/src/hooks/useResizeObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { RefObject, useEffect, useRef } from 'react';
import ResizeObserver from 'resize-observer-polyfill';

export function useResizeObserver(
ref: RefObject<HTMLElement>,
resizeCallback: (arg: ResizeObserverEntry['contentRect']) => void
) {
const resizeObsesrverRef = useRef<ResizeObserver | null>(null);
const onResize = useRef(resizeCallback);

useEffect(() => {
if (ref.current === null) {
return;
}

resizeObsesrverRef.current = new ResizeObserver((entries) => {
onResize.current(entries[0].contentRect);
});
resizeObsesrverRef.current.observe(ref.current);

return () => resizeObsesrverRef.current?.disconnect();
}, [ref]);
}
1 change: 1 addition & 0 deletions ui-kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
export { default as Snackbar } from './components/Snackbar';
export { default as List, ListItem } from './components/List';
export { default as Input } from './components/Input';
export { default as Accordion } from './components/Accordion';
export { Portal } from './contexts/Portal';
export { useToast } from './contexts/Toast';
export { useSnackbar } from './contexts/Snackbar';
Expand Down
42 changes: 42 additions & 0 deletions ui-kit/src/sass/components/_Accordion.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
$animation-duration: 0.3s ease-in-out;

.lubycon-accordion {
border: {
top: 1px solid get-color('gray20');
bottom: 1px solid get-color('gray20');
}
& + & {
border-top: none;
}
&--opened {
.lubycon-accordion__label__icon {
transform: rotate(180deg);
}
}
&__label {
display: flex;
align-items: center;
padding: 16px;
user-select: none;
cursor: pointer;
transition: background-color 0.1s ease-in-out;
&:hover {
background-color: get-color('gray10');
}
&__icon {
margin-right: 16px;
transition: transform $animation-duration;
}
&__text {
color: get-color('gray90');
}
}
&__cover {
transition: height $animation-duration;
overflow: hidden;
&__content {
padding: 8px 16px 16px 50px;
transition: opacity $animation-duration;
}
}
}
4 changes: 2 additions & 2 deletions ui-kit/src/sass/components/_Input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ $description-position: 30px;
background-color: get-color('gray20');
padding: 10px;
border-radius: 8px;
border: 2px solid transparent;
border: 1px solid transparent;
transition: border 0.1s ease-in-out, background-color 0.1s ease-in-out;
box-sizing: border-box;

Expand All @@ -25,7 +25,7 @@ $description-position: 30px;
}

&--focused {
border-color: get-color('gray50');
border-color: get-color('gray100');
background-color: get-color('gray10');
}

Expand Down
1 change: 1 addition & 0 deletions ui-kit/src/sass/components/_index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import './Accordion';
@import './Alert';
@import './Button';
@import './Text';
Expand Down
42 changes: 42 additions & 0 deletions ui-kit/src/stories/Accordion.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { Accordion } from 'src';
import { Meta } from '@storybook/react/types-6-0';

export default {
title: 'Lubycon UI Kit/Accordion',
} as Meta;

export const Default = () => {
return (
<>
<Accordion
label="👀 텍스트가 숨겨져 있어요"
onChange={(v) => console.log(`onChange: ${v}`)}
onOpen={() => console.log('handleOpen')}
onClose={() => console.log('handleClose')}
>
아코디언이 펼쳐지면 아래에 내용이 나옵니다.
<br />
아코디언이 펼쳐지면 아래에 내용이 나옵니다.
<br />
아코디언이 펼쳐지면 아래에 내용이 나옵니다.
<br />
</Accordion>
<Accordion label="🔥 이미지가 숨겨져 있어요">
<img
src="http://cogulmars.cafe24.com/img/04about_img01.png"
alt="귀여운 에비츄"
width="300"
/>
</Accordion>
<Accordion label="제목을 입력하세요">
아코디언이 펼쳐지면 아래에 내용이 나옵니다.
<br />
아코디언이 펼쳐지면 아래에 내용이 나옵니다.
<br />
아코디언이 펼쳐지면 아래에 내용이 나옵니다.
<br />
</Accordion>
</>
);
};
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14974,6 +14974,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=

resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==

resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
Expand Down

0 comments on commit dbdd25b

Please sign in to comment.