콘텐츠로 이동

UI 기본 요소 — 현장 가이드

모던 앱을 보면서 “저건 도대체 뭐라고 부르는 거지?”라고 생각해 본 적이 있다면 — 이 페이지가 바로 여러분을 위한 것입니다.

여러분이 사용하는 모든 인터페이스는 **UI 기본 요소(primitives)**라고 불리는 놀라울 정도로 적은 수의 재사용 가능한 빌딩 블록으로 만들어져 있습니다. 이것들에는 이름이 있습니다. 때로는 이상한 이름도 있습니다. 하지만 그 비유를 한 번 이해하면, 다시는 예전처럼 볼 수 없게 됩니다.

En Parlant~는 이 모든 것을 기본 제공하는 React 컴포넌트 라이브러리인 Mantine을 사용합니다. 여기 여러분의 현장 가이드가 있습니다.


**토스트(Toast)**는 화면 가장자리에서 튀어나와 몇 초간 머물다가 미끄러지듯 사라지는 작은 알림입니다. 토스터에서 식빵이 튀어나오는 것과 비슷한데 — 다만 중력이 반대로 작용해서 결국 다시 들어간다는 점이 다릅니다.

En Parlant~에서 토스트는 우측 하단에 나타납니다(Mantine의 @mantine/notifications 사용). 예를 들면:

  • “Database loaded successfully” —
  • “TTS voice not configured” —
  • “Failed to connect to Lichess” — , 그리고 약간 슬픈 기분이 듭니다

여러분이 요청한 적도 없습니다. 그냥 나타나서 메시지를 전달하고 떠납니다. UI 세계의 전서구 같은 존재입니다.

**카드(Card)**는 테두리나 그림자가 있는 직사각형 컨테이너로, 관련 콘텐츠를 하나로 묶어줍니다. 책상 위에 놓인 색인 카드를 상상해 보세요 — 뚜렷한 경계가 있고, 하나의 내용을 담고 있으며, 집어서 옮길 수 있습니다.

En Parlant~에서 카드는 다음과 같은 곳에 사용됩니다:

  • 데이터베이스 브라우저의 게임 미리보기 (GameCard) — 선수 이름, 결과, 날짜
  • 보드 포지션 편집기 (EditingCard) — 커스텀 포지션을 설정할 때 뒤집는 카드
  • 데이터베이스 페이지의 데이터베이스 항목 — 각 데이터베이스가 그리드에서 자체 카드를 갖습니다

카드에는 Paper라는 가까운 친척이 있는데, 기본적으로 미술학교를 다닌 카드입니다. 같은 아이디어 — 테두리나 그림자가 있는 컨테이너 — 이지만 의미론적 의미가 더 적습니다. Paper는 단지 “나는 표면이야”라고 말합니다. 분석 패널과 데이터베이스 정보 표시 뒤의 배경으로 나타납니다.

현재 En Parlant~에서는 사용되지 않지만 알아두면 좋습니다: **드로어(Drawer)**는 화면 가장자리에서 미끄러져 들어오는 패널로, 책상 서랍 같은 것입니다. (그게 다입니다. 비유의 전부입니다. UI 디자이너들이 그날 매우 직설적이었던 모양입니다.)


**모달(Modal)**은 여러분의 주의를 요구하는 팝업 대화상자입니다. 배경을 회색으로 만들고, 다른 일을 하기 전에 이것부터 처리하도록 강제합니다. UI 패턴의 유아 같은 존재입니다 — “지금 당장 나 좀 봐!”

En Parlant~는 진정으로 여러분의 집중이 필요한 순간에 모달을 사용합니다:

  • “모든 데이터를 정말 삭제하시겠습니까?” (ClearDataModal) — 어떤 데이터를 없앨지 선택할 수 있는 체크박스 포함
  • “업데이트가 있습니다” (UpdateModal) — 다운로드 중 진행 바 표시
  • “게임 가져오기” (ImportModal) — PGN 붙여넣기, FEN 입력, 또는 링크 드롭
  • “새 레퍼토리 만들기” (CreateRepertoireModal) — 이름 짓기, 설정하기, 시작하기
  • “En Parlant~ 정보” (About) — 버전 정보, 크레딧, 일반적인 무대 뒤 이야기

모달은 이렇게 말합니다: “이건 대화야. 지금 해야 해.”

**다이얼로그(Dialog)**는 모달의 예의 바른 사촌입니다. Mantine에서는 본질적으로 같은 컴포넌트이지만, UI 디자인 이론에서 다이얼로그는 더 작고 집중적입니다 — 하나의 질문과 답변. “변경 사항을 저장하시겠습니까?” 예 / 아니오. 끝.

En Parlant~는 저장하지 않은 변경 사항을 폐기할지 확인하는 것처럼 빠른 예/아니오 순간에 ConfirmModal을 사용합니다.


**툴팁(Tooltip)**은 무언가 위에 마우스를 올리면 나타나는 작은 라벨입니다. 여러분이 혼란스러운 표정을 지을 때 답을 속삭여주는 친절한 낯선 사람 같은 존재입니다.

툴팁은 En Parlant~ 곳곳에 있습니다. 거의 모든 아이콘 버튼 위에 마우스를 올리면 하나를 볼 수 있습니다:

  • ↻ 버튼 위에 마우스를 올리면 → “Reload”
  • 잘린 파일 경로 위에 마우스를 올리면 → 전체 경로가 나타남
  • 주석 기호 위에 마우스를 올리면 → 해당 기호의 의미
  • 분석 패널의 수 위에 마우스를 올리면 → 미니어처 보드 미리보기가 나타남

툴팁은 원조 “매뉴얼 불필요”입니다.

**팝오버(Popover)**는 대학원을 다닌 툴팁입니다. 단순히 텍스트 라벨을 보여주는 대신, 버튼, 이미지, 심지어 작은 체스보드까지 무엇이든 담을 수 있습니다.

En Parlant~에서 엔진 분석의 수 위에 마우스를 올리면, 팝오버가 해당 포지션의 보드 미리보기를 렌더링합니다. 툴팁인데, 시각 자료를 가져온 것입니다.

**메뉴(Menu)**는 무언가를 클릭(또는 우클릭)하면 나타나는 액션 목록입니다. 컴퓨팅의 시작부터 사용해왔습니다 — File, Edit, View. 이미 잘 아시죠.

En Parlant~에는 두 가지 종류가 있습니다:

  • 상단 바 메뉴 — Mantine의 Menu 컴포넌트로 만들어진 클래식한 File / Edit / View 메뉴 바. 오른쪽에 키보드 단축키가 표시됩니다. 연금을 받을 만큼 오래된 디자인 패턴입니다.
  • 컨텍스트 메뉴 — 게임 트리에서 수를 우클릭하면 Promote Variation, Delete from Here, Copy Line 같은 옵션이 나옵니다. 더 세련된 마감을 위해 mantine-contextmenu 라이브러리를 사용합니다.

메뉴에는 자체 하위 용어가 있습니다: Menu.Target(클릭하는 대상), Menu.Dropdown(나타나는 목록), Menu.Item(각 옵션), 그리고 Menu.Divider(“여기서부터 다른 섹션”이라고 말하는 가는 선).


이것들은 레이아웃의 땅콩버터와 젤리입니다.

  • Stack은 항목을 세로로 배치합니다 — 하나 위에 하나씩. 책을 쌓는 것처럼.
  • Group은 항목을 가로로 배치합니다 — 나란히. 선반에 체스 기물을 줄 세우는 것처럼.

각각 100개 이상의 파일에서 사용됩니다. 앱 전체에서 가장 흔한 컴포넌트입니다. 무언가가 깔끔하게 배치되어 있다면, Stack 또는 Group이 그 역할을 하고 있는 것입니다.

Flex는 Stack과 Group의 만능 사촌입니다. 항목들이 줄바꿈되거나, 늘어나거나, 줄어들거나, 역순으로 배치되거나, 그 외에 예상대로 작동하지 않아야 할 때, Flex가 CSS Flexbox 모델을 완전히 제어하며 나섭니다.

분석 패널의 수 목록에서 사용됩니다 — 텍스트처럼 줄바꿈되며 흐르는 수의 시퀀스가 보이시나요? 바로 Flex가 무거운 짐을 지고 있는 것입니다.

Box는 탑햇을 쓴 <div>입니다. 그 자체로는 아무것도 하지 않습니다 — 그냥 일반적인 컨테이너입니다. 하지만 Mantine의 스타일링 props를 받기 때문에, “이 다른 것 주위에 뭔가가 필요해”라는 상황에서 엄청나게 유용한 래퍼가 됩니다.

Box는 150개 이상의 파일에서 사용됩니다. 컴포넌트 라이브러리의 만능 테이프입니다.

이름 그대로의 역할을 합니다. 무언가를 가운데에 놓습니다. 가로로. 세로로. 둘 다. 끝.

빈 상태에서 나타납니다 — 연결된 계정이 없거나, 로드된 데이터베이스가 없거나, 아직 데이터가 없을 때. 큰 아이콘, 메시지, 그리고 완벽하게 위치를 잡아주는 Center. 중앙에서 벗어난 빈 상태를 좋아하는 사람은 없습니다.

심플한 그리드입니다. 몇 개의 열인지 알려주면 창 크기가 변할 때 리플로우되는 깔끔한 그리드로 카드를 배치합니다. 데이터베이스 페이지에서 반응형 그리드로 데이터베이스 카드를 배치하는 데 사용됩니다.

콘텐츠가 넘칠 때 스크롤되는 컨테이너입니다. CSS overflow: auto로 공짜일 것 같지만 — 어느 정도는 맞습니다 — ScrollArea는 스타일이 적용된 스크롤바, 관성 스크롤, 오버플로우 감지를 추가합니다. 게임 트리 패널, 분석 라인, 그리고 콘텐츠가 상자보다 커질 수 있는 모든 곳을 감쌉니다.

AppShell은 마스터 레이아웃 — 전체 애플리케이션의 골격입니다. 헤더가 어디에 가고, 사이드바가 어디에 가고, 메인 콘텐츠가 어디에 들어가는지 정의합니다. 다른 모든 것이 그 안에 중첩됩니다.

한 번만 사용합니다. 집의 기초와 같습니다.

Portal은 마법의 웜홀입니다. 컴포넌트를 DOM 트리에서 정상적인 위치가 아닌 다른 곳에 — 보통 문서의 루트에 — 렌더링합니다. 이것이 바로 팝오버와 보드 미리보기가 overflow: hidden이 설정된 부모 컨테이너에 의해 잘리지 않는 이유입니다.

Portal은 절대 보이지 않습니다. 그 효과만 보일 뿐입니다. UI 컴포넌트의 무대 스태프입니다.


텍스트가 있는 클릭 가능한 직사각형. 이건 아시죠. Mantine에서 버튼은 **변형(variants)**으로 제공됩니다:

  • filled — 단색, 높은 강조. “나를 클릭해, 나는 중요해.”
  • outline — 테두리만. “나는 옵션이야, 하지만 부담 없어.”
  • subtle — 거의 보이지 않는. “필요하면 여기 있어.”
  • default — 중립적인 중간 지대.

En Parlant~는 150개 이상의 파일에서 이 모든 것을 사용합니다. 가장 흔한 패턴: 모달 하단의 Group에 배치된 버튼들 — “Cancel”(subtle)과 “Confirm”(filled). 클래식합니다.

ActionIcon은 텍스트 라벨을 포기하고 아이콘 라이프스타일에 완전히 전념한 버튼입니다. 컴팩트하고, 정사각형이며, 목적을 전달하기 위해 아이콘(그리고 툴팁)에 의존합니다.

툴바 버튼, 패널 컨트롤, 닫기 버튼 — 모두 ActionIcon입니다. 액션을 제공하는 가장 공간 효율적인 방법입니다.

Switch는 작은 전등 스위치처럼 생긴 토글입니다. 왼쪽으로 넘기고, 오른쪽으로 넘기고. 켜기. 끄기.

설정에서 스위치는 이진 선택을 제어합니다:

  • 소리 켜기/끄기
  • TTS 내레이션 활성화/비활성화
  • 다크 모드 / 라이트 모드

물리적 비유가 너무 뛰어나서 할머니, 할아버지도 이해하십니다.

Slider는 값을 선택하기 위해 핸들을 앞뒤로 드래그하는 트랙입니다. 납작해진 볼륨 노브 같은 것입니다.

En Parlant~에서 슬라이더는 다음에 사용됩니다:

  • 글꼴 크기 — 드래그하여 조절
  • 볼륨 — 드래그하여 조절 (당연하죠)
  • CPU 코어 — 엔진이 몇 개의 코어를 사용할지?
  • 해시 메모리 — 엔진의 해시 테이블에 얼마나 많은 RAM을?

범위를 선택하기 위한 핸들이 두 개RangeSlider도 있습니다 — Elo 레이팅(예: 2000–2200)과 기간으로 게임을 필터링하는 데 사용됩니다.

정확히 하나만 선택되는 버튼 행입니다. 자동차 라디오 프리셋 같습니다 — 하나를 누르면 나머지가 빠집니다.

다음 사이의 전환에 사용됩니다:

  • 분석 차트에서 센티폰 평가 vs. 승/무/패 퍼센티지
  • 다른 수 표기 형식
  • 엔진 라인 수

이것들은 아시죠. 체크박스는 “해당하는 것 모두 선택”, Select 드롭다운은 “이 목록에서 하나 선택”. 앱 전체의 설정, 필터, 폼에서 나타납니다. Clear Data 모달에는 특히 만족스러운 체크박스 세트가 있어서 스스로의 파괴를 선택할 수 있습니다.

입력하는 동안 완성을 제안하는 텍스트 입력란입니다. 데이터베이스 브라우저의 선수 검색에서 사용됩니다 — 이름을 입력하기 시작하면 일치하는 선수가 아래에 나타납니다.

FileInput은 클릭하면 파일 선택기를 엽니다 — PGN 파일을 로드하기 위한 import 모달에서 사용됩니다. DateInput은 날짜를 선택하기 위한 캘린더를 팝업합니다 — 날짜별 게임 필터링에 사용됩니다. 덜 정밀한 선택이 필요할 때를 위한 MonthPickerInputYearPickerInput도 있습니다(Lichess 데이터베이스의 월별 필터링, Masters 데이터베이스의 연별 필터링).


무언가가 얼마나 진행되었는지 보여주기 위해 왼쪽에서 오른쪽으로 채워지는 가로 막대입니다. 앱 업데이트(다운로드 진행률) 중에 사용되며 — 더 창의적으로 — 분석 패널에서 승/무/패 확률을 표시하는 데 사용됩니다. 세 가지 색상 섹션(흰색, 회색, 검은색)이 비례적으로 막대를 채웁니다. 진행 표시기로 위장한 작은 막대 차트입니다.

Loader는 “작업 중이에요, 잠깐만요”라고 말하는 회전 애니메이션입니다. 데이터베이스가 로딩 중이거나, 선수 통계를 가져오는 중이거나, 비동기 작업이 진행 중일 때 나타납니다.

Skeleton은 아직 로드되지 않은 콘텐츠의 모양을 모방하는 회색 플레이스홀더입니다. 빈 화면이나 스피너 대신, 카드와 텍스트가 들어갈 곳의 유령 윤곽선이 보입니다. 로딩 상태의 건축 설계도 같은 것입니다 — “진짜 가구는 오는 중이에요.”

데이터베이스 페이지에서 카드 그리드가 로드되는 동안 스켈레톤을 사용합니다. 스피너만 있는 것보다 훨씬 우아합니다.

로딩 중인 컴포넌트 위에 덮어씌우는 반투명 블랭킷입니다. 분석 차트에서 사용됩니다 — 오버레이 뒤로 차트를 여전히 볼 수 있지만 어둡게 처리되고, 위에 스피너가 있습니다. “아직 여기 있어요, 업데이트 중일 뿐이에요.”


Badge는 작은 색상 라벨입니다 — 컨퍼런스의 명찰처럼. En Parlant~에서 SpeedBadge 컴포넌트는 색상 코딩과 함께 시간 컨트롤을 표시하기 위해 뱃지를 사용합니다:

  • 🩷 핑크 — UltraBullet
  • 🔴 빨강 — Bullet
  • 🟠 주황 — Blitz
  • 🟢 초록 — Rapid
  • 🔵 파랑 — Classical
  • 🟣 보라 — Correspondence

각 뱃지는 작고, 다채롭고, 게임 속도를 한눈에 즉시 전달합니다.

keyboard의 줄임말입니다. 특유의 돌출된 키 스타일로 키보드 키를 표시합니다. 키 바인딩 설정에서 Ctrl + Z 같은 것을 보여주는 데 사용됩니다. 여러분 키보드의 실제 물리적 키를 CSS로 재현한 것입니다. 유쾌한 스큐어모피즘입니다.


섹션을 구분하는 가로선입니다. 그게 다입니다. 선입니다. 하지만 스타일이 적용된 선이며, 선택적으로 가운데에 텍스트 라벨을 넣을 수 있습니다 — import 모달에서 “paste a PGN”과 “enter a FEN”을 구분하는 ”— OR —” 구분선처럼.

30개 이상의 파일에서 Divider를 사용합니다. 알고 보면, 분리해야 할 것들이 정말 많습니다.

Collapse는 부드러운 애니메이션으로 펼쳐지고 접히는 섹션입니다. 클릭하면 드러나고, 클릭하면 숨겨집니다. 데이터베이스 필터 옵션에서 사용됩니다 — 항상 모든 필터를 볼 필요는 없기 때문에, 원할 때까지 Collapse 뒤에 숨겨둡니다.


TipTap 위에 구축된 완전한 위지위그(WYSIWYG) 텍스트 편집기입니다. 굵게, 기울임, 목록 등 다양한 기능이 있습니다. 게임 주석 작성에 사용됩니다 — 어떤 수가 왜 훌륭했는지 또는 끔찍했는지 설명하기 위해 추가하는 해설 노트입니다.

이 기본 요소는 단일 세포보다는 복합 유기체에 가깝습니다. 내부적으로는 TipTap + ProseMirror + Mantine 스타일링입니다. 하지만 사용자 관점에서는 서식이 작동하는 텍스트 상자일 뿐입니다.

@mantine/charts(Recharts를 래핑)의 AreaChart평가 그래프를 구동합니다 — 분석 패널 하단에 흐르는 파형입니다. 중앙선 위의 흰색 영역은 백이 우세함을, 아래의 검은색은 흑이 우세함을 의미합니다.

차트의 어느 지점이든 클릭하면 해당 수로 이동할 수 있습니다. 인터랙티브 데이터 시각화와 체스 분석의 만남입니다. 게임 단계(오프닝 → 미들게임 → 엔드게임)를 표시하는 커스텀 툴팁과 참조선까지 있습니다.

mantine-datatable 라이브러리를 통해 — 정렬, 페이지네이션, 행 선택, 필터링 기능을 갖춘 완전한 기능의 데이터 테이블입니다. 데이터베이스 브라우저의 게임 목록, 선수 목록, 토너먼트 목록을 구동합니다. 자신이 체스 앱에 있다는 것을 아는 스프레드시트입니다.


일부 기본 요소는 스스로 가시적인 것을 렌더링하지 않지만 다른 모든 것이 작동하게 합니다:

  • MantineProvider — 전체 앱을 감싸고 테마(색상, 다크 모드, 컴포넌트 기본값)를 제공합니다
  • Portal — 렌더링된 콘텐츠를 DOM의 다른 부분으로 텔레포트합니다
  • useHotkeys — 키보드 단축키를 연결하는 훅(컴포넌트가 아님)
  • useForm — 폼 상태, 유효성 검사, 에러 처리를 관리합니다
  • useDebouncedValue — 검색을 실행하기 전에 입력이 멈출 때까지 기다립니다. 예의 바른 훅입니다.

참고용으로, En Parlant~에서 사용되는 모든 기본 요소 패밀리를 기능별로 그룹화했습니다:

카테고리기본 요소
레이아웃AppShell, Box, Center, Flex, Group, Stack, SimpleGrid, ScrollArea, Portal
표면Card, Paper
오버레이Modal, Tooltip, Popover, Menu, ContextMenu, LoadingOverlay
알림Toast (@mantine/notifications 사용)
버튼Button, ActionIcon, CloseButton, ThemeIcon
입력TextInput, Textarea, NumberInput, Select, Autocomplete, Checkbox, Switch, Slider, RangeSlider, SegmentedControl, FileInput, DateInput, MonthPickerInput, YearPickerInput
데이터 표시Table, DataTable, Badge, Code, Kbd, Rating, Image
타이포그래피Text, Title, Anchor
진행 상황Progress, Loader, Skeleton
구조Divider, Collapse
리치 콘텐츠RichTextEditor (TipTap), AreaChart (Recharts)
테마MantineProvider, createTheme, useColorScheme
useForm, useHotkeys, useToggle, useDebouncedValue, useClickOutside, useElementSize

모두 Mantine v8와 해당 에코시스템 패키지에서 제공됩니다: @mantine/core, @mantine/notifications, @mantine/dates, @mantine/charts, @mantine/form, @mantine/tiptap, mantine-datatable, 그리고 mantine-contextmenu.


다음에 화면 구석에서 작은 메시지가 미끄러져 올라오는 걸 보면 — 그게 토스트입니다. 게임 미리보기를 둘러싼 직사각형 — 그게 카드입니다. 콘텐츠가 로드되기 전에 깜빡이는 회색 덩어리들 — 그게 스켈레톤입니다.

이름을 배우고 나면, 어디서나 보이기 시작합니다. 모든 앱. 모든 웹사이트. 같은 몇십 개의 기본 요소가 수천 가지 다른 방식으로 재배열되어 있습니다.

끝까지 레고입니다.