본문 바로가기
pynecone 튜토리얼

Pynecone tutorial: 투두리스트 스타일 매기기

by 일코 2023. 1. 21.
반응형

지난 포스팅에서는 로직 파트까지 구현하고 마쳤습니다.

 

Pynecone tutorial: Todo앱의 로직파트 작성하기

지난 시간에는 화면의 컴포넌트를 모두 구성해보았습니다. Pynecone tutorial: Todo앱을 만들어봅시다. 지난 포스팅에서는 터미널에서 pc run을 실행하여 pynecone 프로젝트를 초기화할 때 생성되는 프로

martinii.fun

 

이번 마지막 포스팅에서는 CSS스타일을 매겨서
우리의 앱을 더 예쁘게 만들어보겠습니다.

본격적으로 시작하기 전에
Pynecone 웹앱에 스타일을 매기는 세 가지 방법에 대해
간략히 설명드리겠습니다.

참고로 일반적인 CSS 스타일 적용방법은

  1. 인라인 스타일(Inline style)
  2. 내부 스타일 시트(Internal style sheet)
  3. 외부 스타일 시트(External style sheet)

위의 세 가지 방법으로 구분된다는 점은 알고 계실 거라고 생각합니다.

Pynecone에서도 (일반적으로) 스타일을 적용할 수 있는 방법이 세 가지라고 말씀드렸는데,
위의 CSS 방식을 그대로 따르는 건 아니고,

①글로벌 스타일, ②컴포넌트 스타일,
그리고 ③인라인 스타일이라는
아래의 세 가지 방식으로 나뉩니다.

 

① 글로벌 스타일

css파일을 따로 만드는 대신
파이썬 딕셔너리 자료형을 통해
스타일시트를 생성하고, 이를 앱에 적용하는 방식입니다.

Pynecone에서는 css 스타일 지정시에 snake_case 컨벤션을 따릅니다.
빼기 기호(-)는 모두 언더스코어(_)로 대체해야 합니다. 
style = {
    "font_family": "Comic Sans MS",
    "font_size": "16px",
}

app = pc.App(state=State, style=style)

 

② 컴포넌트 스타일

각 컴포넌트별로 특정 스타일을 기본으로 지정해둘 수 있습니다.
이 때 주의할 부분은,
컴포넌트 객체가 아닌 컴포넌트 클래스(첫 글자가 대문자)를 대상으로
지정해야 한다는 점입니다.

accent_color = "#f81ce5"
style = {
    "::selection": {
        "background_color": accent_color,
    },
    pc.Text: {
        "font_family": "Inter",
    },
    pc.Divider: {
        "margin_bottom": "1em",
        "margin_top": "0.5em",
    },
    pc.Heading: {
        "font_weight": "500",
    },
    pc.Code: {
        "color": accent_color,
    },
}

app = pc.App(state=State, style=style)

 

③ 인라인 스타일

특정 컴포넌트에만 인라인으로 스타일을 지정할 수 있습니다.
이 때에는 키를 파라미터로(문자열이 아님), 값을 인수로 작성하면 됩니다.

pc.text(
    "Hello World",
    background_image="linear-gradient(271.68deg, #EE756A 0.75%, #756AEE 88.52%)",
    background_clip="text",
    font_weight="bold",
    font_size="2em",
)

참고로 위의 서식을 적용한 텍스트는 아래처럼 나타납니다.

예뻐서 붙여보았습니다.

 

그럼 본격적으로 우리 앱에다
스타일을 적용해보겠습니다.

지난 시간까지 우리가 작성했던 앱의 모습과,
코드를 먼저 보여드리겠습니다.

아무 스타일도 매겨져 있지 않은 상태입니다.

경미한 디버깅 작업이 있었습니다.
아래 코드에 화살표로 표시해 두었습니다.

① 공란도 할 일 목록에 추가되어버리는 문제 개선
② 작업 완료버튼 클릭시 동일명의 작업이 전부 지워지는 문제 개선
import pynecone as pc


class State(pc.State):
    items = [
        "아침먹고 땡",
        "점심먹고 땡",
        "저녁먹고 땡",
    ]
    new_item = ""

    def add_item(self):
        if self.new_item:                     # <-- ①
            self.items += [self.new_item]
            self.new_item = ""

    def finish_item(self, item):
        self.items.remove(item)               # <-- ②
        self.items = [i for i in self.items] 


def render_item(item):
    return pc.list_item(pc.hstack(
        pc.button(on_click=lambda: State.finish_item(item)),
        pc.text(item)
    ))


def index():
    return pc.container(
        pc.vstack(
            pc.heading("할 일 목록"),
            pc.input(on_blur=State.set_new_item, placeholder="안녕하세요?"),
            pc.button("추가", on_click=State.add_item),
            pc.ordered_list(
                pc.foreach(State.items, lambda item: render_item(item)),
            ),
        ),
    )


app = pc.App(state=State)
app.add_page(index)
app.compile()

 

우선 앱 전체의 배경색부터 칠해봅시다.

현재는 pc.vstack에 배경색(bg)이 기본값인 흰색(#ffffff)이라서 테두리가 보이지 않으니까,
pc.vstack 컴포넌트의 마지막에 bg="#eeeeee"(옅은 회색) 을 한 번 추가해보겠습니다.

샵(#) 뒤에서부터 차례대로 두 자리씩 각각 Red(00), Green(00), Blue(00) 라는 뜻입니다.
#ffffff는 흰색, #000000은 검정색, 
def index():
    return pc.container(
        pc.vstack(
            pc.heading(...),
            pc.input(...),
            pc.button(...),
            pc.ordered_list(...),
            bg="#000000",         # <--
        ),
    )

vstack에 bg="eeeeee" 적용

적당한 회색이 되었네요.
이번엔 직사각형의 모서리를 조금 둥글게(border_radius) 깎아봅시다.
1em 정도로요.

def index():
    return pc.container(
        pc.vstack(
            pc.heading(...),
            pc.input(...),
            pc.button(...),
            pc.ordered_list(...),
            bg="#eeeeee",
            border_radius="1em"  # <--
        ),
    )

모서리가 깎이니까 훨씬 예뻐 보이네요.
근데 안쪽 여백이 없으니까
마지막 태스크나, 상단의 제목이 너무 딱 붙어서
보기 좋지 않네요.
vstack에 안쪽여백(padding)을 1em 추가해봅시다.
여백 넣는 김에 바깥쪽 여백(margin)도 1em 추가합니다.

def index():
    return pc.container(
        pc.vstack(
            ...
            pc.ordered_list(...),
            bg="#eeeeee",
            border_radius="1em",
            padding="1em",       # <--
            margin="1em",        # <--
        ),
    )

이번엔 앱에 입체적인 느낌을 줄 수 있게
아래 라인을 입력해서
그림자(shadow)를 추가해보겠습니다.

box_shadow="0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",

너무 복잡하죠?
위 라인은 아래처럼 단축입력이 가능합니다.

shadow="lg"

이렇게만 코드에 추가해봅시다.

"lg" 외에도 "sm", "md", "xl", "2xl", "inner" 등의 인수도 적용하실 수 있습니다.
def index():
    return pc.container(
        pc.vstack(
            ...
            pc.ordered_list(...),
            bg="#eeeeee",
            border_radius="1em",
            padding="1em",
            margin="5em",
            shadow="lg",
        ),
    )

shadow="lg" 적용

그림자가 붙으니까 훨씬 입체적으로 보이네요.

이제 다른 컴포넌트에도 스타일을 적용해봅시다.

우선 input 컴포넌트와 "추가"버튼에 모두 bg="white" 를 적용해 흰색으로 만들어봅시다.

pc.container(
    pc.vstack(
        pc.heading(...),
        pc.input(
            on_blur=State.set_new_item,
            placeholder=r"write down your to-do.",
            bg="white",  # <--
        ),
        pc.button("추가", on_click=State.add_item, bg="white"),  # <--
        pc.ordered_list(...),
        ...
    ),
)

인풋, 추가버튼 컴포넌트에 bg="white" 적용

마지막으로 render_item 함수 안에 정의된
할일목록의 버튼과 텍스트를 조금만 수정해보고 마치겠습니다.

우선 버튼이 잘 보이지 않으니,
버튼을 흰색으로 바꾸고, border로 테두리를 그려보겠습니다.

def render_item(item):
    return pc.list_item(
        pc.hstack(
            pc.button(
                on_click=lambda: State.finish_item(item),
                bg="white",                        # <--
                border="1px solid blue",           # <--
            ),
            pc.text(item),
        ),
    )

할일 버튼에 배경색과 테두리 적용

버튼이 불필요하게 큰 것 같지 않으신가요?
height를 적용해서 납작~하게 만들어보겠습니다.

def render_item(item):
    return pc.list_item(
        pc.hstack(
            pc.button(
                on_click=lambda: State.finish_item(item),
                bg="white",
                border="1px solid blue",
                height="1em",                      # <--
            ),
            pc.text(item),
        ),
    )

마지막으로 할일목록의 글자크기가
앱 크기에 비해 상대적으로 너무 작아 보입니다.
font_size를 1em으로 변경해보겠습니다.

이 때 주의할 점은 pc.text에 바로 font_size를 적용해버리면
좌측의 인덱스에는 폰트사이즈가 적용되지 않으므로,
pc.text가 아닌 상위의 pc.list_item에다 font_size를 적용해 주셔야 합니다.

def render_item(item):
    return pc.list_item(
        pc.hstack(
            pc.button(
                on_click=lambda: State.finish_item(item),
                bg="white",
                border="1px solid blue",
                height="1em",
            ),
            pc.text(item),
        ),
        font_size="1.25em",  # <--
    )

우리 앱이 어떤 모습으로 바뀌었는지 한 번 볼까요?

짜잔! 너무 멋지게 바뀌었네요.

더 멋지게 꾸며보고 싶은 분들도 많으실텐데요.

투두리스트 튜토리얼은 여기서 마치고, 다른 포스팅으로 돌아오겠습니다.

수고하셨습니다!

다음 포스팅은 UX 관련한 사족같은 포스팅입니다.
꼭 읽으실 필요는 없습니다.

다음 포스팅

2023.01.22 - [pynecone 튜토리얼] - Pynecone tutorial: 추가버튼클릭시 인풋 컴포넌트 비우기

 

Pynecone tutorial: 추가버튼클릭시 인풋 컴포넌트 비우기

지난 포스팅까지, 파인콘 데모 갤러리에서 가장 쉬워 보이는 투두리스트에 대한 튜토리얼을 마쳤습니다. 2023.01.19 - [pynecone 튜토리얼] - Pynecone tutorial: Todo앱을 만들어봅시다. 2023.01.20 - [pynecone 튜

martinii.fun

 

반응형

댓글0