개발조각

[React-native CLI] RN스타일, styled-components로 화면 구현하기(Home화면 스타일) 본문

React-Native/[프로젝트] 택시 운행관리 기록장

[React-native CLI] RN스타일, styled-components로 화면 구현하기(Home화면 스타일)

개발조각 2024. 3. 12. 22:08
728x90
반응형

이번에는 메인스크린을 스타일링해보도록 하겠습니다.

 

이전 포스팅을 보셨으면 알겠지만 스타일을 styled-components를 사용해서 스타일링했습니다.

RN에서 styled-components 설치 방법에 대해 알고 싶다면 아래 링크를 클릭해 주세요!

https://development-piece.tistory.com/438

 

[React-native CLI] styled-components 설치 및 세팅

리액트 네이티브에서도 styled-components가 사용가능하더라고요. 이전에는 가능할 줄 몰라서 안 쓰기도 했고, 요즘 다시 전통적인 css가 좋아서 썼는데 리액트 네이티브에서 css를 사용해보니 css보다

development-piece.tistory.com

 

그동안 styled-components를 React에서만 사용해 봤지 React-native에서는 처음 사용해 봐서 세팅하는데 좀 오래 걸렸습니다.

styled-component가 React에서는 사용가능한 기능이 react-native에서는 사용불가능한 기능들도 많아서 실험하면서 하다 보니 힘드네요ㅠㅠ

이뿐만 아니라 react-native에서는 모든 속성과 값을 지원하지 않아서, 하나하나 다 파악하면서 스타일을 짜야되서 생각보다 배로 더 걸리더라고요...ㅠ

 

RN에서 스타일과 css의 차이점


이 포스팅에서는 styled-components를 사용했으며, 이에 대한 차이점에 대한 설명입니다.

(모든 차이점을 분석한 내용은 아니며, 사용해 보면서 느낀 차이점에 대해 작성해 보겠습니다.)

 

만약 styled-components를 사용 안 하고 RN스타일로만 스타일링을 하고 싶다면 아래 링크를 참고해 주세요.

https://wit.nts-corp.com/2020/03/23/6014

 

React Native UI 개발 시작하기 | WIT블로그

최근 React Native로 개발하는 프로젝트의 UI개발을 맡아 진행하였습니다. 컴포넌트나 스타일 등 웹에서 작업하던 것과 달라 초기에 많은 시행착오를 겪었는데요 저와 같이 처음 React Native를 경

wit.nts-corp.com

 

웹에서는 상위 태그에 css를 지정하면 하위 태그까지 반영이 되지만, RN은 그렇지 않다.

웹에서는 css를 작성할 시 선택자(#,.)에 따라 우선순위도 결정이 되고 상위 태그에 css를 지정하면 하위까지 반영이 되지만

RN은 이러한 css의 상속이 존재하지 않으며, 컴포넌트마다 각각 적용이 된다.

 

grid  못 사용한다.

다행히도 flex사용이 가능하지만 grid는 사용하지 못합니다.

 

웹에서 flex는 가로정렬이 기본값이지만 RN에서는 원하는 방향을 설정해 주어야 된다.

flex-direction: row;

 

 

position: absolute 할 때 relative를 사용 안 해도 되는 것 같다.

relative를 사용안해도 감싸져 있는 컴포넌트에 맞춰서 적용되는 것 같다.

 

%가 적용되지 않는다.

css에서 100%, 50%는 자주 사용이 되는데 이를 사용할 수 없다 다 px로 계산해서 적용시켜 줘야 된다.

50% 쓰면 계속 오류가 납니다.

// 다 못 사용한다.
transform: translate(-50%, -50%);
border-radius: 50%;

 

 

RN에서 styled-components 사용 시 차이점


Theme, Global Style 사용할 수 없다.

그래서 Theme는 다른 방식으로 적용해 보았습니다.

 

scss처럼 상위집합 같은 방식을 적용할 수 없다.

export const Greeting = styled.div`
    position: absolute;

    top: 25%;
    left: 0;

    max-width: 337px;
    height: 264px;

    h1 {
        margin-bottom: 24px;
    }

    ${({ theme }) => theme.device.mobile} {
        width: 100%;
    }
`;

 

 

그래도 RN이 React, Next, TS에서 styled-components를 사용하는 방법보다는 사용하기 쉬웠습니다.

오히려 제약사항이 많다 보니 안 쓰면 그만이라;;; 오히려 좋았습니다.

 

완성한 화면


 

폴더


 

 

styled-components는 제가 편하다 생각하는 방식으로 작업한 거라 일반적인 방식이 아닐 수 있습니다.

 

types/types.ts

export interface RecordType {
  date: string;
  card: number;
  cash: number; // 현금
  lpgInjectionVolume: number; // LPG 주입량
  lpgUnitPrice: number; // LPG 단가
  mileage: number; // 주행거리
  businessDistance: number; // 영업거리
  toll: number; // 통행료
  operatingAmount: number; // 영업금액
  lpgChargeAmount: number; // LPG 충전 금액
  fuelEfficiency: number; // 연비
  lpgUsage: number; // LPG 사용량
}

export interface RecordBoxType {
  title: string;
  state: number;
  unit?: '원' | 'km' | 'km/L' | 'L';
  option?: 'basics' | 'orange';
}

 

styles/Theme.ts

const colors = {
  // main
  main: '#FFA800',
  mainDeep: '#FF7B00',
  mainLight: '#FFEFD2',

  // grey
  grey: '#D9D9D9',
  darkGrey: '#888888',
  lightGrey: '#F5F5F7',

  black: '#333',

  red: '#F36E51',
  blue: '#519CF3',
};

const fonts = {
  // size
  baseSize: '14px',
  smallSize: '12px',
  mediumSize: '18px',
  largeSize: '20px',

  // weight
  redular: 'NotoSansKRRegular',
  medium: 'NotoSansKRMedium',
  bold: 'NotoSansKRBold',

  // line-height 1.25
  baseHight: '17.5px',
  smallHeight: '15p',
  mediumHeight: '22.5px',
  largeHeight: '25px',
};

const fontCommon = {
  base: `
    font-size: ${fonts.baseSize};
    line-height: ${fonts.baseHight};
  `,
  small: `
    font-size: ${fonts.smallSize};
    line-height: ${fonts.smallHeight};
  `,
  medium: `
    font-size: ${fonts.mediumSize};
    line-height: ${fonts.mediumHeight};
  `,
  large: `
    font-size: ${fonts.largeSize};
    line-height: ${fonts.largeHeight};
  `,
};

const common = {
  flexCenter: `
    display: flex;
    justify-content: center;
    align-items: center;
  `,
  flexRowCenter: `
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
  `,
};

const Theme = {
  colors,
  fonts,
  fontCommon,
  common,
};

export default Theme;

 

styles/home.styles.ts

// react, react-native
import {Text, TouchableOpacity, View} from 'react-native';

// library
import styled from 'styled-components';

// style
import Theme from './Theme';

// Home
export const Home = {
  container: styled(View)`
    flex: 1;
  `,
  bottomContainer: styled(View)`
    padding: 0 16px;
    margin-bottom: 96px;
  `,
};

// YearBusinessAmount
export const YearBusinessAmount = {
  container: styled(View)`
    padding: 20px 0;
    background: ${Theme.colors.mainLight};
    ${Theme.common.flexCenter}
  `,
  title: styled(Text)`
    color: ${Theme.colors.mainDeep};
    font-family: ${Theme.fonts.medium};
    ${Theme.fontCommon.base}
  `,
  valueText: styled(Text)`
    color: ${Theme.colors.mainDeep};
    font-family: ${Theme.fonts.bold};
    ${Theme.fontCommon.large}
    margin-top: 4px;
  `,
};

// MonthCalendar
interface PositionType {
  position?: boolean;
}

export const MonthCalendar = {
  container: styled(View)`
    padding: 8px 16px;
  `,
  centerWrap: styled(View)`
    ${Theme.common.flexRowCenter}
    gap: 8px;
  `,
  iconButton: styled(TouchableOpacity)<PositionType>`
    ${Theme.common.flexCenter}
    width: 32px;
    height: 32px;

    /* props에 position이 있으면 position 주기 */
    ${props =>
      props.position &&
      `
        position: absolute;
        top: 8px;
        right: 0;
      `}
  `,
  text: styled(Text)`
    font-family: ${Theme.fonts.medium};
    ${Theme.fontCommon.large}
    color: ${Theme.colors.black};
  `,
};

// MonthBusinessAmount
export const MonthBusinessAmount = {
  container: styled(View)`
    padding: 16px 0;
    border-radius: 10px;
    background: ${Theme.colors.lightGrey};
  `,
  topWrap: styled(View)`
    ${Theme.common.flexCenter}
  `,
  title: styled(Text)`
    font-family: ${Theme.fonts.medium};
    ${Theme.fontCommon.base}
    color: ${Theme.colors.mainDeep};
  `,
  moneyValueText: styled(Text)`
    font-family: ${Theme.fonts.bold};
    ${Theme.fontCommon.large}
    color: ${Theme.colors.mainDeep};
    margin-top: 4px;
  `,
  bottomWrap: styled(View)`
    ${Theme.common.flexRowCenter}
    margin-top: 16px;
  `,
  bottomBox: styled(View)`
    ${Theme.common.flexCenter}
    flex: 1;
  `,
  subTitle: styled(Text)`
    ${Theme.fontCommon.base}
    color: ${Theme.colors.black};
  `,
  subValueText: styled(Text)`
    font-family: ${Theme.fonts.bold};
    ${Theme.fontCommon.medium}
    color: ${Theme.colors.black};
    margin-top: 4px;
  `,
  line: styled(View)`
    width: 1px;
    height: 100%;
    background: ${Theme.colors.grey};
  `,
};

// MonthRecordInfo
export const MonthRecordInfo = {
  container: styled(View)`
    margin-top: 8px;
  `,
};

// CreateButton
export const CreateButton = {
  button: styled(TouchableOpacity)`
    position: absolute;
    bottom: 20px;
    right: 16px;
    ${Theme.common.flexCenter}
    width: 56px;
    height: 56px;
    border-radius: 56px;
    background: ${Theme.colors.mainLight};
  `,
};

 

screens/Home.tsx

// react, react-native
import {ScrollView} from 'react-native';

// library

// assets, utils, realm

// component
import YearBusinessAmount from '../components/home/YearBusinessAmount.tsx';
import MonthCalendar from '../components/home/MonthCalendar.tsx';
import MonthBusinessAmount from '../components/home/MonthBusinessAmount.tsx';
import MonthRecordInfo from '../components/home/MonthRecordInfo.tsx';
import CreateButton from '../components/home/CreateButton.tsx';

// style
import {Home as Style} from '../styles/home.styles.ts';

const Home = () => {
  return (
    <Style.container>
      <ScrollView>
        {/* 연간 영업 금액 */}
        <YearBusinessAmount />

        {/* 월별 운행 기록 */}
        <Style.bottomContainer>
          {/* 월별 달력 */}
          <MonthCalendar />

          {/* 이번달 영업 금액 */}
          <MonthBusinessAmount />

          {/* 이번달 운행 정보 */}
          <MonthRecordInfo />
        </Style.bottomContainer>
      </ScrollView>

      {/* create 버튼 */}
      <CreateButton />
    </Style.container>
  );
};

export default Home;

 

components/home/YearBusinessAmount.tsx

// react, react-native

// library

// assets, utils, realm

// component

// style
import {YearBusinessAmount as Style} from '../../styles/home.styles';

const MonthBusinessAmount = () => {
  return (
    <Style.container>
      <Style.title>연간 영업 금액</Style.title>
      <Style.valueText>3,000,000원</Style.valueText>
    </Style.container>
  );
};

export default MonthBusinessAmount;

 

components/home/MonthCalendar.tsx

// react, react-native
import {View} from 'react-native';
import {SvgXml} from 'react-native-svg';

// library

// assets, utils, realm
import {svg} from '../../assets/svg';

// component

// style
import {MonthCalendar as Style} from '../../styles/home.styles';

const MonthCalendar = () => {
  return (
    <Style.container>
      <Style.centerWrap>
        <Style.iconButton>
          <View>
            <SvgXml xml={svg.prev} />
          </View>
        </Style.iconButton>

        <Style.text>2024년 3월</Style.text>

        <Style.iconButton>
          <View>
            <SvgXml xml={svg.next} />
          </View>
        </Style.iconButton>
      </Style.centerWrap>

      <Style.iconButton position={true}>
        <View>
          <SvgXml xml={svg.turn} />
        </View>
      </Style.iconButton>
    </Style.container>
  );
};

export default MonthCalendar;

 

components/home/MonthBusinessAmount.tsx

// react, react-native

// library

// assets, utils, realm

// component

// style
import {MonthBusinessAmount as Style} from '../../styles/home.styles';

const MonthBusinessAmount = () => {
  return (
    <Style.container>
      <Style.topWrap>
        <Style.title>영업금액</Style.title>
        <Style.moneyValueText>2,000,000원</Style.moneyValueText>
      </Style.topWrap>

      <Style.bottomWrap>
        <Style.bottomBox>
          <Style.subTitle>카드</Style.subTitle>
          <Style.subValueText>2,000,000원</Style.subValueText>
        </Style.bottomBox>

        <Style.line></Style.line>

        <Style.bottomBox>
          <Style.subTitle>현금</Style.subTitle>
          <Style.subValueText>2,000,000원</Style.subValueText>
        </Style.bottomBox>
      </Style.bottomWrap>
    </Style.container>
  );
};

export default MonthBusinessAmount;

 

components/home/MonthRecordInfo.tsx

// react, react-native

// library

// assets, utils, realm

// component
import RecordBox from '../common/RecordBox';

// style
import {MonthRecordInfo as Style} from '../../styles/home.styles';
import {RecordBox as RecordBoxStyle} from '../../styles/common.styles';

const MonthRecordInfo = () => {
  return (
    <Style.container>
      <RecordBoxStyle.wrap>
        <RecordBox title="LPG 주입량" state={10} unit="L" />
        <RecordBox title="LPG 단가" state={10} />
        <RecordBox title="LPG 충전 금액" state={10} option="orange" />
      </RecordBoxStyle.wrap>

      <RecordBoxStyle.wrap>
        <RecordBox title="주행거리" state={10} unit="km" />
        <RecordBox title="영업거리" state={10} unit="km" />
        <RecordBox title="통행료" state={10} />
      </RecordBoxStyle.wrap>

      <RecordBoxStyle.wrap>
        <RecordBox title="연비" state={10} unit="km/L" option="orange" />
        <RecordBox title="LPG 사용량" state={10} unit="L" option="orange" />
      </RecordBoxStyle.wrap>
    </Style.container>
  );
};

export default MonthRecordInfo;

 

styles/common.styles.ts

// react, react-native
import {Text, View} from 'react-native';

// library
import styled from 'styled-components';

// style
import Theme from './Theme';
import {RecordBoxType} from '../types/types';

type OptionType = Pick<RecordBoxType, 'option'>;

export const RecordBox = {
  wrap: styled(View)`
    ${Theme.common.flexRowCenter}
    gap: 8px;
    margin-top: 8px;
  `,
  box: styled(View)<OptionType>`
    flex: 1;
    padding: 10px 6px;
    border-radius: 10px;
    background: ${props =>
      props.option === 'orange'
        ? Theme.colors.mainLight
        : Theme.colors.lightGrey};
  `,
  title: styled(Text)<OptionType>`
    ${Theme.fontCommon.base};
    color: ${props =>
      props.option === 'orange' ? Theme.colors.mainDeep : Theme.colors.black};
    text-align: center;
  `,
  valueTextWrap: styled(View)`
    margin-top: 6px;
    padding: 4px 0;
    width: 100%;
    border-radius: 20px;
    background: #fff;
  `,
  valueText: styled(Text)`
    font-family: ${Theme.fonts.bold};
    ${Theme.fontCommon.base};
    color: ${Theme.colors.black};
    text-align: center;
  `,
};

 

components/common/RecordBox.tsx

// react, react-native

// library

// assets, utils, realm, type
import {RecordBoxType} from '../../types/types';

// component

// style
import {RecordBox as Style} from '../../styles/common.styles';

const RecordBox = ({
  title,
  state,
  unit = '원',
  option = 'basics',
}: RecordBoxType) => {
  return (
    <Style.box option={option}>
      <Style.title option={option}>{title}</Style.title>
      <Style.valueTextWrap>
        <Style.valueText>{`${state}${unit}`}</Style.valueText>
      </Style.valueTextWrap>
    </Style.box>
  );
};

export default RecordBox;

 

components/home/CreateButton.tsx

// react, react-native
import {View} from 'react-native';
import {SvgXml} from 'react-native-svg';

// library

// assets, utils, realm
import {svg} from '../../assets/svg';

// component

// style
import {CreateButton as Style} from '../../styles/home.styles';

const CreateButton = () => {
  return (
    <Style.button>
      <View>
        <SvgXml xml={svg.plus} fill="#FFA800" />
      </View>
    </Style.button>
  );
};

export default CreateButton;

 

728x90
반응형
Comments