Front-end/React.js

12. Styled-components : Dialog 만들기 - 컴포넌트 스타일링 | 벨로퍼트

AGAL 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 가 잘 작동하는지 확인해보세요.

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

반응형