Skip to Content
Packages@howmuchhome-web/ui

@howmuchhome-web/ui

얼마집 디자인 시스템 패키지. web/dashboard(Next.js)와 native(Expo) 앱이 공유하는 공통 UI 컴포넌트를 제공합니다.

monorepo 내부 패키지로 별도 설치 없이 사용합니다. 컴포넌트를 새로 만들 때는 packages/ui/CLAUDE.md의 완성 체크리스트를 함께 참고하세요. native 소비 세부(Metro 아이콘 치환·NativeWind 등)는 크로스플랫폼 UIMetro와 의존성에 있습니다.

진입점

package.jsonexports가 정의하는 진입점입니다.

진입점대상실제 경로
@howmuchhome-web/ui/v2web/dashboard — 현행 컴포넌트v2/index.ts
@howmuchhome-web/ui/nativenative(Expo) — native 구현이 있는 컴포넌트만native/index.ts
@howmuchhome-web/ui/v1레거시 (수정 금지)v1/index.ts
@howmuchhome-web/ui/utils플랫폼 무관 유틸 컴포넌트/훅utils/index.ts

web은 /v2, native는 /native. 같은 /v2 경로로 자동 분기하지 않는 이유(번들러 조건·export * 제약)와 native 배럴에 등록된 컴포넌트 목록은 크로스플랫폼 UI를 참고하세요.

// web / dashboard import {Button, Dialog, useOverlay} from '@howmuchhome-web/ui/v2'; // native (Expo) import {Button, Form} from '@howmuchhome-web/ui/native';

plain HTML을 쓰지 않는 이유

버튼·입력 등을 <button>/<input>으로 직접 만들지 말고 반드시 디자인 시스템 컴포넌트를 씁니다. Button 하나가 색/사이즈 토큰, 로딩 스피너 전환, Interaction.Overlay(눌림 피드백), 비동기 autoLoading, disabled 오버레이, 폼 제출 연동을 모두 캡슐화합니다. 이 동작들을 각 화면에서 다시 구현하면 디자인·접근성·상호작용 일관성이 깨집니다.

// ✅ <Button style="filled" color="primary" onClick={handleClick}>확인</Button> // ❌ <button className="rounded border px-4 py-2 ...">확인</button>

디렉토리 구조

패키지 루트(packages/ui/) 바로 아래에 진입점 디렉토리가 있습니다(src/ 없음).

packages/ui/ ├── v2/ # web 현행 컴포넌트 (+ .native.tsx / .shared.ts) │ ├── Button/ Dialog/ Overlay/ Form/ ... │ ├── common/ # 색 매핑(colors.ts)·공유 타입·InputView 등 │ ├── provider/ # HowmuchhomeUIProvider (UIProvider) │ └── utils/ # OnPortal / Popover / InfiniteScroll 등 내부 유틸 ├── native/index.ts # native 전용 배럴 (v2의 .native 구현만 re-export) ├── v1/ # 레거시 └── utils/ # 플랫폼 무관 컴포넌트/훅 (InputWrapperForIOS, usePreventScroll 등)

각 컴포넌트 폴더의 파일 규약:

파일역할
Foo.tsxweb 구현 (DOM/Tailwind, 'use client' 필수)
Foo.native.tsxnative 구현 (RN + react-native-css)
Foo.shared.tsweb/native 공유 순수 로직·상수 (DOM/RN import 금지)
index.tsnamed export
Foo.stories.tsx / Foo.stories.native.tsxStorybook

대표 컴포넌트 (v2)

Button

<Button style="filled" color="primary" onClick={handleClick}>확인</Button> <Button style="transparent" color="error" size="small">삭제</Button>
Prop타입비고
style'filled' | 'transparent'필수
color'primary' | 'secondary' | 'tertiary' | 'error' | 'error-secondary' | 'white'필수
size'extra-small' | 'small' | 'medium' | 'large'기본 medium
startIcon / endIconReactElement<IconProps>크기·currentColor를 자동 주입
fullWidthboolean기본 false
loadingboolean스피너 표시
autoLoadingboolean | 'persist-on-success'기본 true. onClick이 Promise면 자동 로딩. 'persist-on-success'는 성공 시 로딩 유지(에러는 re-throw 필요)
underlineboolean텍스트 밑줄

관련 export: SimpleTextButton, IconButton, ToggleButton(v2/Button/index.ts).

Dialog

DialogRoot에 하위 컴포넌트를 Object.assign으로 붙인 합성(compound) 컴포넌트입니다.

const dialog = useOverlay(); <Dialog controller={dialog.controller}> <Dialog.Header>제목</Dialog.Header> <Dialog.Body>내용</Dialog.Body> <Dialog.Footer> <Dialog.Buttons> <Button style="filled" color="primary" onClick={dialog.close}>확인</Button> </Dialog.Buttons> </Dialog.Footer> </Dialog>
서브 컴포넌트파일
Dialog (root)Dialog/DialogRoot.tsx
Dialog.HeaderDialog/DialogHeader.tsx
Dialog.BodyDialog/DialogBody.tsx
Dialog.FooterDialog/DialogFooter.tsx
Dialog.ButtonsDialog/DialogFooterButtons.tsx

useOverlay

오버레이(Dialog/Drawer 등) 열기·닫기 컨트롤러 훅입니다.

const {controller, open, close} = useOverlay({ initialState: false, disableHistoryStack: false, // 기본 false: close()가 history.back()으로 popstate 트리거 onOpen: () => {/* 열릴 때 form 초기화 등 */}, });

close()Promise<void>를 반환합니다(web은 즉시 resolve, native와 API 호환). 기본값에서 web close()history.back()을 호출해 안드로이드 뒤로가기/popstate와 오버레이 닫힘을 연동합니다. 이 동작을 끄려면 disableHistoryStack: true.

이 외 다수 컴포넌트가 있습니다(v2/index.ts 전체 목록): Alert/Confirm, TextField/PasswordField/SearchBar/Textarea, Checkbox/RadioGroup/ToggleSwitch, Chip/Tag/Badge, Tabs/SegmentedControl/SegmentTab, Accordion/Collapsible, Dropdown/DropdownMenu/DropdownSelect, Toast/Snackbar, Scaffold/AppBar/PageLayout, Table/DataView, Carousel, DatePicker, ScrollPicker, SecurityKeypad 등.

Provider

앱 최상위 layout.tsxHowmuchhomeUIProvider(export 이름은 providerUIProvider)를 한 번 배치합니다. Portal 루트(#_root_portal), popstate 스택(오버레이 뒤로가기), window 레이아웃 컨텍스트, iOS 포커스 보정, 컨텍스트 메뉴 차단을 담당합니다.

import {HowmuchhomeUIProvider} from '@howmuchhome-web/ui/v2'; <HowmuchhomeUIProvider preventContextMenu> {children} </HowmuchhomeUIProvider>

preventContextMenu는 기본 true(웹뷰에서 네이티브 느낌). 데스크톱 대시보드처럼 브라우저 우클릭 메뉴를 허용하려면 false.

색 / 테마 시스템

색 시스템은 @howmuchhome-web/style 패키지가 단일 소스입니다.

  • packages/style/colors.css — CSS 변수 정의. 원시 팔레트(--color-blue-500 등, OKLCH 기준 RGB triple)와 그 위의 semantic v2 토큰(--color-fill-solid-brand-primary, --color-label-normal-primary, --color-border-normal-secondary, --color-background-normal-primary …)을 계층으로 둡니다.
  • packages/style/colors/colorV4.ts — 동일 팔레트의 JS/TS 상수. CSS 변수를 쓸 수 없는 곳(RN 아이콘 색 등)에서 hex 값이 필요할 때 사용합니다. @howmuchhome-web/style에서 colorV4twMerge를 export합니다.

컴포넌트에서는 hex를 직접 쓰지 않고 semantic Tailwind 클래스를 씁니다. Tailwind 테마가 카테고리 접두(fill/label/border/background)를 벗겨 클래스명에 매핑하므로, 예를 들어 클래스 bg-solid-brand-primary → 변수 --color-fill-solid-brand-primary, text-normal-primary--color-label-normal-primary로 해석됩니다.

Button 색 결정 로직은 v2/common/colors.ts에 단일 소스로 있습니다. getButtonBackgroundClassName·getButtonTextColorClassname(style, color)를 받아 semantic 클래스 문자열을 반환하고, web Button.tsx와 native Button.native.tsx같은 함수를 사용합니다. native는 이 클래스에 이미 담긴 group-disabled:/disabled: 변형을 런타임에 bare 클래스로 변환(Button.shared.ts)해서 disabled 색을 적용합니다 — 색 분기 자체는 common/colors.ts 한 곳에만 둡니다.

아이콘 / 에셋 연계

아이콘은 별도 패키지 **@howmuchhome-web/icon**입니다.

  • @howmuchhome-web/icon — 아이콘 컴포넌트 배럴(libs/index.ts)
  • @howmuchhome-web/icon/typesIconProps 등 타입

ButtonstartIcon/endIconReactElement<IconProps>를 받아, size가 없으면 버튼 size에 맞춘 크기를, color가 없으면 currentColor를(native는 텍스트 색 hex를) 자동 주입합니다.

native에서는 Metro가 web 아이콘 구현을 native 구현으로 치환합니다. 세부는 Metro와 의존성을 참고하세요.

새 컴포넌트 추가

전체 체크리스트는 packages/ui/CLAUDE.md를 따릅니다. 요지:

  1. web은 v2에만 추가합니다. v1/은 레거시라 절대 수정하지 않습니다.
  2. v2/[ComponentName]/ComponentName.tsx('use client' 필수) + index.ts named export + v2/index.tsexport * from './[ComponentName]' 추가.
  3. .stories.tsx 작성 (Controls 동작하도록 argTypes).
  4. native에서도 쓰는 컴포넌트라면 플랫폼별로 분리합니다: Foo.tsx(web) / Foo.native.tsx(RN + react-native-css) / Foo.shared.ts(공유 순수 로직). native 구현은 native/index.ts 배럴에 등록해야 @howmuchhome-web/ui/native로 노출됩니다.
  5. native className은 react-nativeView/Text가 아니라 react-native-css/components의 컴포넌트에 적용해야 먹습니다. 색은 web과 동일한 semantic 클래스를 쓰고, 분기가 필요하면 common/colors.ts에 둡니다.

분리 규약·native 전용 컴포넌트 동작(Scaffold, BottomSheet, Reveal, OTPInput 등)의 세부는 크로스플랫폼 UI에 정리되어 있습니다.

Last updated on