ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 12. Styled-components : Dialog 만들기 - 컴포넌트 스타일링 | 벨로퍼트
    Front-end/React.js 2020. 7. 21. 13:18
    반응형

     

    이번에는 기존 화면을 가리게 되면서 정보를 보여주게 되는 Dialog 컴포넌트를 만들어보겠습니다.

     

    이 컴포넌트를 만드는 과정에서 우리가 아까 만들었던 Button 컴포넌트를 재사용하게 됩니다.

    트랜지션 효과는 나중에 구현을 해주겠습니다.

     

    우선 components 디렉터리에 Dialog.js 파일을 생성 후 다음 코드를 입력해보세요.

    /src/components/Dialog.js :

    import React from 'react';
    import styled from 'styled-components';
    import Button from './Button';
    
    const DarkBackground = styled.div`
      position: fixed;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: rgba(0, 0, 0, 0.8);
    `;
    
    const DialogBlock = styled.div`
      width: 320px;
      padding: 1.5rem;
      background: white;
      border-radius: 2px;
      h3 {
        margin: 0;
        font-size: 1.5rem;
      }
      p {
        font-size: 1.125rem;
      }
    `;
    
    const ButtonGroup = styled.div`
      margin-top: 3rem;
      display: flex;
      justify-content: flex-end;
    `;
    
    function Dialog({ title, children, confirmText, cancelText }) {
      return (
        <DarkBackground>
          <DialogBlock>
            <h3>{title}</h3>
            <p>{children}</p>
            <ButtonGroup>
              <Button color="gray">{cancelText}</Button>
              <Button color="pink">{confirmText}</Button>
            </ButtonGroup>
          </DialogBlock>
        </DarkBackground>
      );
    }
    
    Dialog.defaultProps = {
      confirmText: '확인',
      cancelText: '취소'
    };
    
    export default Dialog;

    우리가 h3, 과 p 를 스타일링 때 굳이 다음과 같이 따로 따로 컴포넌트를 만들어주지 않아도

    const Title = styled.h3``;
    const Description = styled.p``;

    styled-components 에서도 Nested CSS 문법을 사용 할 수 있기 때문에 DialogBlock 안에 있는 h3 와 p 에게 특정 스타일을 주고 싶다면 다음과 같이 작성 할 수 있답니다.

    const DialogBlock = styled.div`
      h3 { ... }
      p { ... }
    `;

    자, 이제 이 컴포넌트를 App 에 렌더링해보세요.

    /src/App.js :

    // ...
    import Dialog from './components/Dialog';
    
    // ...
    function App() {
      return (
        <ThemeProvider
          theme={{
            palette: {
              blue: '#228be6',
              gray: '#495057',
              pink: '#f06595'
            }
          }}
        >
          <>
            <AppBlock>
              // ...
            </AppBlock>
            <Dialog
              title="정말로 삭제하시겠습니까?"
              confirmText="삭제"
              cancelText="취소"
            >
              데이터를 정말로 삭제하시겠습니까?
            </Dialog>
          </>
        </ThemeProvider>
      );
    }
    // ...

    Dialog 컴포넌트를 예시 내용과 함께 AppBlock 하단에 넣어주었으며, ThemeProvider 내부는 하나의 리액트 엘리먼트로 감싸져있어야 하기 때문에 AppBlock 과 Dialog 를 <></> 으로 감싸주었습니다.

    지금 보면 이 Dialog 에서는 취소 버튼과 삭제 버튼의 간격이 조금 넓어보이는 느낌이 있는데요, 만약에 styled-components로 컴포넌트의 스타일을 특정 상황에서 덮어쓰는 방법에 대해서 알아보겠습니다.

     

    Dialog.js 에서 다음과 같이 ShortMarginButton 을 만들고 기존 Button 을 대체시켜보세요.

    /src/components/Dialog.js :

    //...
    
    const ShortMarginButton = styled(Button)`
      & + & {
        margin-left: 0.5rem;
      }
    `;
    
    function Dialog({ title, children, confirmText, cancelText }) {
      return (
        <DarkBackground>
          <DialogBlock>
            <h3>{title}</h3>
            <p>{children}</p>
            <ButtonGroup>
              <ShortMarginButton color="gray">{cancelText}</ShortMarginButton>
              <ShortMarginButton color="pink">{confirmText}</ShortMarginButton>
            </ButtonGroup>
          </DialogBlock>
        </DarkBackground>
      );
    }
    
    // ...

    여백이 줄어들었나요? 이렇게 컴포넌트의 스타일을 커스터마이징 할 때에는 해당 컴포넌트에서 className props 를 내부 엘리먼트에게 전달이 되고 있는지 확인해주어야 합니다.

    const MyComponent = ({ className }) => {
      return <div className={className}></div>
    };
    
    const ExtendedComponent = styled(MyComponent)`
      background: black;
    `;

    참고로 우리가 만든 Button 컴포넌트의 경우에는 ...rest 를 통하여 전달이 되고 있습니다.

     

    컴포넌트의 모양새를 모두 갖추었으면 열고 닫을 수 있는 기능을 구현해봅시다. Dialog 에서 onConfirm 과 onCancel 을 props 로 받아오도록 하고 해당 함수들을 각 버튼들에게 onClick 으로 설정해주세요.

     

    그리고, visible props 도 받아와서 이 값이 false 일 때 컴포넌트에서 null 을 반환하도록 설정해주세요.

    /src/components/Dialog.js :

    // ...
    
    function Dialog({
      title,
      children,
      confirmText,
      cancelText,
      onConfirm,
      onCancel,
      visible
    }) {
      if (!visible) return null;
      return (
        // ...
      );
    }
    
    // ...

    그 다음에는, App 컴포넌트에서 useState 를 사용하여 Dialog 를 가시성 상태를 관리해보세요.

    /src/App.js :

    // ...
    
    function App() {
      const [dialog, setDialog] = useState(false);
      const onClick = () => {
        setDialog(true);
      };
      const onConfirm = () => {
        console.log('확인');
        setDialog(false);
      };
      const onCancel = () => {
        console.log('취소');
        setDialog(false);
      };
    
      return (
        <ThemeProvider
          theme={{
            palette: {
              blue: '#228be6',
              gray: '#495057',
              pink: '#f06595'
            }
          }}
        >
          <>
            <AppBlock>
              <Button size="large" color="pink" fullWidth onClick={onClick}>
                삭제
              </Button>
            </AppBlock>
            <Dialog
              title="정말로 삭제하시겠습니까?"
              confirmText="삭제"
              cancelText="취소"
              onConfirm={onConfirm}
              onCancel={onCancel}
              visible={dialog}
            >
              데이터를 정말로 삭제하시겠습니까?
            </Dialog>
          </>
        </ThemeProvider>
      );
    }
    
    // ...

    맨 아래에 있는 큰 핑크색 버튼의 이름을 "삭제" 로 변경 후 해당 버튼을 누르면 우리가 만든 Dialog 가 보여지도록 설정을 했고,

    Dialog 에 onConfirm, onCancel, visible 값을 전달해주었습니다.

     

    브라우저에서 삭제 버튼을 눌러서 Dialog 가 잘 작동하는지 확인해보세요.

    버튼 눌렀을 때 콘솔에 확인/취소 텍스트가 출력되는지도 확인하세요.

    반응형

    댓글

Luster Sun