Form controls (Input · Select · Picker)
진행 중 (Form controls — work in progress)
- 텍스트 필드(
input/textarea)와 드롭다운(select)의 공유 사이즈 체계. PR #247에서 기본 높이 40→32px 통일 +ds-input--XX/ds-select--XX스케일 정리. !important없이 사이즈가 적용됨 — 기본 규칙이--XX모디파이어를:not()으로 제외해 모디파이어가 자연 specificity로 이김. (강제 최소화 설계)- ⚠️
--ds-textfield-*/--ds-dropdown-*토큰이 raw hex로 정의 — 검토 필요 사항.
규칙 (강제) · 코딩 에이전트용 — 사람은 접힌 채로 두어도 됩니다
Input · Select · 필드 구성의 강제 규칙. 상세·토큰·피커는 아래 본문.
- 기본 32px. 사이즈 클래스를 안 주면
input/select/박스버튼은 medium 32px가 자동(이전 40→32). 요소 규칙이라 클래스 없이도 전 서비스 적용됨. - 사이즈는
ds-input--XX/ds-select--XX스케일(52/48/40/32/28/24)로만. input·select가 같은 스케일이라 나란히 두면 높이가 flush. 크기(height/font/padding)는 native 규칙 +--XX모디파이어가 담당하고 Tailwind는 마진·배치 유틸로만 — 기본 규칙이:not([class*="ds-input--"])로 모디파이어를 제외해--XX가!important없이 이김. - 필드 구성 스택: 라벨 → 필수
*→ 입력칸 → 설명 → 에러(위→아래). 필수 = 라벨 끝<span class="text-red-400">*</span>+ HTMLrequired병행. - 설명 =
.ds-form-help(12px·입력칸 아래 6px·항상 표시). 색은 필드 상태 자동 추종(rest muted → focus black → error red), sibling 조합자로 동작하므로 입력칸과 같은 부모의 뒤쪽 형제여야 함(JS 토글 불필요). 앱 로컬text-b6 neutral-400/500재조합 금지. - 에러 =
.ds-form-error(danger red·12px·아래 6px·x-show토글), 점프 방지 정렬 폼은.ds-form-error-slot(min-height 17px). 입력칸 보더는 공유ds-input--error/ds-select--error표준(admin 로컬.is-error금지). - 숫자 =
ds-input--number— 스피너를 평소 숨기고 hover/focus 시에만 노출 +tabular-nums. 숫자 처리만 담당하므로 크기(ds-input--XX)·너비(w-full)·정렬(text-center)과 조합. - 상태 토큰: hover border
#c1c1c1· focus border#1b1b1b(select은 ring) · disabledopacity:.45+not-allowed· error border--ds-color-danger. 이 클래스/토큰들은 공유design-system.css가 단일 출처(앱 로컬 복제 금지). - 체크박스 =
ds-checkbox, 라디오 =ds-radio(한 세트 — 값은 같고 모양만 사각/원형). 클래스 없는 맨<input type="checkbox|radio">는 브라우저 기본 컨트롤(OS 파랑) 로 렌더돼 DS 팔레트를 벗어납니다 — 반드시 클래스를 붙이세요. 크기는 16px 기본 /--lg20px 둘뿐이고,accent-color·직접 그린 가짜 체크박스는 금지. 선택지에 보조설명이 붙어 라벨이 2줄이 되면--lg(20px) +items-start+mt-0.5(컨트롤을 첫 줄에 맞춤) ·gap-2.5· 제목text-b5/ 설명text-b6 text-label-muted· 항목 간space-y-1.5— 2줄 조합. 상세: 체크박스 · 라디오. - 스위치 =
ds-switch(작은 자리는--sm, 이 둘뿐).<input type="checkbox">에만 붙입니다 —<button>/<div>로 트랙+손잡이를 직접 그리지 마세요(키보드·스크린리더가 사라집니다). 켜짐 primary 주황 / 꺼짐neutral-300/ disabled 45%는 CSS가 강제하므로 색을 따로 주지 마세요. 글자 라벨이 옆에 없으면aria-label필수. 용도 경계: 즉시 반영되는 기능 on/off만 스위치, 저장을 눌러야 반영되는 폼 값은 체크박스, 예/아니오 상태 선택은 불리언select(Modal). 상세: 스위치. - 피커(날짜·월·시간): 커스텀 우선(
dsDatePicker/dsTimePicker+ include 한 줄), 네이티브는 대안. 모달 안 레이어·닫기 규칙은 Modal. - 메뉴 패널 =
.ds-menu+.ds-menu__item(아이콘+라벨은--row, 선택은--selected). 패널의 padding·border·shadow는.ds-menu가 가지므로 래퍼에bg-white border rounded shadow-* py-*를 따로 붙이지 마세요. 파괴적 항목(삭제)은ds-menu__item--danger— hover에서만 의미색 red.hover:bg-red-50같은 즉석 유틸로 위험 항목을 만들지 마세요. 고를 수 없는 항목은disabled(또는aria-disabled="true")만 주면 됩니다 — 글자가 disable 색으로 내려가고 hover 배경이 사라집니다.bg-neutral-50 text-neutral-400 cursor-not-allowed같은 조건부 유틸을 직접 붙이지 마세요. - 모바일(<768):
ds-input--48/ds-select--48이 권장 기본값(손가락 타깃 — 강제 아님, 사이즈 고르기). 사이즈는 항상 사이즈 클래스(--52/--48/--40/--32…)로 고르고 픽셀을 직접 쓰지 마세요. ⚠️ 모바일에서 입력칸을 탭하면 iOS가 화면을 자동 확대합니다(글자 16px 미만) — 아직 규칙을 정하지 않은 미해결 항목이니 임의로 정하지 말고 그 섹션을 읽으세요. DS 사이즈 클래스엔md:프리픽스가 안 붙으므로, 모바일에서만 크기를 바꾸려면 앱main.css의@media (max-width:767px)에서 스코프 잡아 덮으세요. 상세: Modal → 모바일.
라이브 미리보기
이 페이지에 등록된 인터랙티브 컴포넌트를 한눈에 — 직접 조작해 보고, 각 항목의 **설명 더보기 →**로 상세(클래스·토큰·정책)로 이동하세요. 모두 실제
design-system.css규칙으로 렌더됩니다.
텍스트필드 · 드롭다운 · textarea · 숫자
플레이그라운드에서 크기·채움(fill)·error·disabled를 바꿔 보세요 — 같은 상태가 input·select·textarea에 동시 반영(입력 시 hover/focus도 실제 동작). 맨 아래 숫자(ds-input--number) 행은 스피너가 평소 숨겨졌다가 hover/focus 시 나타나는 걸 직접 확인할 수 있고, 컨트롤의 숫자 토글로 플레이그라운드에서도 켤 수 있습니다.
ds-input--number — 스피너는 hover/focus 시에만, tabular-nums 현재: 32px · 입력값: —— 크기·상태를 바꿔 보세요 (실제 .ds-input/.ds-select). 설명(.ds-form-help)은 입력/드롭다운 아래 6px, focus 시 muted→black. error 토글 시 같은 설명 줄이 red로 바뀌고 보더도 red.
설명 더보기 → 사이즈 스케일 · 토큰 · 숫자 입력 · 필드 구성(설명·에러)
메뉴 패널 (ds-menu)
커스텀 옵션 목록(트리·체크·라벨/구분선)용 플로팅 패널. 선택 항목은 회색 바로 강조됩니다.
파괴적 항목은 ds-menu__item--danger — 삭제처럼 되돌리기 어려운 항목에 얹어요. 평상시엔 일반 항목과 똑같고 hover에서만 의미색 red(--color-secondary-red)로 경고해요. 버튼의 ds-btn--red와 같은 의미 축이에요. 아래 "삭제"에 마우스를 올려 보세요.
메뉴 항목에 hover 유틸을 직접 붙이지 마세요
hover:bg-red-50 hover:text-red-600 같은 즉석 유틸로 위험 항목을 만들지 마세요 — ds-menu__item--danger가 그 역할이에요. (admin 조직 트리 우클릭 메뉴가 유틸 조합에서 이 변형으로 이관됐어요.)
달력 (날짜 · 월 선택)
날짜 = 커스텀 달력(우선) · 시스템 기본(type="date")은 대안 · 월 = 커스텀 월 선택기. 직접 눌러 보세요.
시간 입력 (Time input)
커스텀 시간 입력 다이얼로그 — 오전/오후 드롭다운 + 시·분 키보드 입력 + ▲▼ 스텝. 12시간제로 표시하고 24시간제로 저장합니다. 트리거를 눌러 보세요 (17 입력 → 오후 5시 자동 변환).
클릭 → 시/분 키보드 입력 · ▲▼ 스텝(시 1↔12·분 0↔59 랩) · 오전/오후 드롭다운. 17 입력 → 오후 5시 자동 변환. 숫자 아님/무변경이면 확인 비활성. 저장은 24시간제 HH:MM.
날짜 + 시간 결합
네이티브 datetime-local 대신, 한 팝업에 달력 + 커스텀 시간 폼을 합친 결합 뷰입니다(공지 만료일시 등). 트리거를 눌러 보세요 — 달력(좌)에서 날짜, 시간 폼(우)에서 오전·오후/시·분.
필드 구성 (Field anatomy)
하나의 텍스트 필드는 위에서 아래로 라벨 → (필수 *) → 입력칸 → 설명 → 에러 순서로 쌓입니다. 설명과 에러는 **같은 자리(입력칸 아래 6px·12px)**를 공유해서, 에러가 떴다 사라져도 레이아웃이 흔들리지 않습니다.
회사 이메일을 입력하면 초대 메일이 발송돼요.
입력칸을 클릭(focus)하면 설명(.ds-form-help)이 muted→black, 에러 토글 시 red로 바뀝니다 — 상태 색은 CSS sibling 조합자로 자동. 설명과 에러는 같은 6px·12px 슬롯을 공유.
| 파트 | 클래스 / 마크업 | 스펙 |
|---|---|---|
| 라벨 | <label> + text-b5 font-medium text-label-assistive | 12px(b5) · 입력칸 위 6px(mb-1.5) |
| 필수 표시 | 라벨 끝에 <span class="text-red-400">*</span> | red-400(#ff5f53) · 입력칸엔 HTML required도 함께 |
| 입력칸 | input.ds-input--40 등 | 사이즈 스케일 |
| 입력칸 에러 보더 | ds-input--error / ds-select--error | border = --ds-color-danger(focus 시 ring) |
| 설명(description) | .ds-form-help | 12px · 입력칸 아래 6px · 항상 표시 · 색은 필드 상태 추종(rest=muted → focus=black → error=red) |
| 에러 메시지 | .ds-form-error (높이 예약은 .ds-form-error-slot) | danger red(#e63224) · 12px · 입력칸 아래 6px · x-show로 토글 |
설명 (description) — .ds-form-help
입력 규칙·맥락을 항상 보여 주는 보조 줄입니다(x-show 불필요). 12px·입력칸 아래 6px은 고정이고, 색은 필드 상태를 따라갑니다 — 평소엔 눈에 안 띄게, 입력 중엔 또렷하게, 에러일 땐 경고로:
| 필드 상태 | 설명 색 | 토큰 |
|---|---|---|
| 기본 (focus out) | muted 회색 | --ds-label-fg-muted (#8f8f8f) |
| 선택 (focus in) | black | --ds-label-black (#1b1b1b) |
| 에러 | danger red | --ds-color-danger (#e63224) |
<input class="ds-input--40 w-full" placeholder="user@example.com" />
<p class="ds-form-help">회사 이메일을 입력하면 초대 메일이 발송돼요.</p>상태 색은 CSS 하나로 자동 동작합니다(JS 토글 불필요) — .ds-form-help가 입력칸의 :focus·ds-input--error를 sibling 조합자로 읽습니다. 따라서 .ds-form-help는 입력칸과 같은 부모의 (뒤쪽) 형제여야 합니다(필드 구성 스택 그대로). 에러는 focus보다 우선(에러+focus여도 red).
왜 표준 클래스인가 — 그동안 설명 줄은 앱마다
text-b6 text-neutral-400/500 mt-0.5~1.5로 제각각이었습니다(색·간격 드리프트)..ds-form-help로 크기(12px)·간격(6px)·상태 색(muted→black→red)을 한 곳에 고정해 에러 줄과 세로 위치가 정확히 맞도록 통일합니다.
에러 메시지 — .ds-form-error / .ds-form-error-slot
유효성 에러를 입력칸 아래에 띄웁니다. danger red·12px·아래 6px. 보통 x-show로 조건부 노출하며, **입력칸 보더는 ds-input--error**로 같이 빨강 처리합니다(메시지+보더 한 쌍).
<input class="ds-input--40 w-full"
:class="errors.email && 'ds-input--error'" />
<!-- 토글형: 에러일 때만 자리 차지 -->
<p x-show="errors.email" class="ds-form-error" x-text="errors.email"></p>.ds-form-error—x-show로 보였다 사라지는 기본형. 평소엔 자리를 차지하지 않습니다..ds-form-error-slot—min-height:17px로 빈 상태에도 한 줄 높이를 예약합니다. 에러 토글 시 폼이 위아래로 점프하면 안 되는 정렬 폼(예: 직원 추가/수정 위저드)에 사용.
단일 출처 정비 — 두 메시지 클래스는 원래
apps/admin/src/styles/main.css에만 있던 로컬 규칙이었고, 이번에 공유design-system.css로 승격했습니다. 앞으로 전 앱이 같은 클래스를 씁니다(앱 로컬 복제 금지). 입력칸 보더는 admin의.is-error가 아니라 **공유ds-input--error**를 표준으로 합니다.
단일 출처
| 파트 | 파일 |
|---|---|
| 스타일 | packages/static/styles/design-system.css (native input/select/textarea 규칙 + .ds-input--XX/.ds-select--XX) |
| 토큰 | --ds-textfield-* (border/hover/focus/radius/fg/placeholder) · --ds-dropdown-* (bg/border/text/radius) · --ds-dropdown-chevron |
토큰 소유권: 필드 룩(테두리·radius·글자·placeholder·chevron)은
--ds-textfield-*/--ds-dropdown-*= DS 소유. 사이즈(height/font/padding)는 native 규칙 +--XX모디파이어에 직접 정의. 마진 등 배치는 Tailwind 유틸.
기본값 — 32px (PR #247)
사이즈 클래스를 안 주면 medium 32px가 기본입니다. (이전 40px → 32px로 변경; design-system.css가 단일 출처, Figma는 색만 미러)
| 요소 | 기본 height | font | 비고 |
|---|---|---|---|
input (--XX 없음) | 32px | 12px | padding 0 12px |
select (--XX 없음) | 32px | 12px | chevron 우측 16px |
textarea | auto (min 5rem) | 12px | padding 10px 16px, resize: vertical |
| 박스 버튼 | min-height 32px | — | .ds-tab-item(40px)·.icon-btn(정사각) 제외 |
강제 없이 동작: 기본 규칙은
input:not(...):not([class*="ds-input--"])/select:not(.ds-select--24)…:not(.ds-select--52)로 사이즈 모디파이어를 제외합니다. 그래서ds-input--48같은 클래스를 주면!important없이도 기본 32px 규칙과 경쟁하지 않고 그대로 적용됩니다.
사이즈 스케일
텍스트 필드와 드롭다운이 같은 스케일(52/48/40/32/28/24)이라, 어떤 사이즈든 나란히 두면 높이가 flush하게 맞습니다.
ds-input--XX
| 클래스 | height | padding | font |
|---|---|---|---|
ds-input--52 | 52px | 0 16px | 14px |
ds-input--48 | 48px | 0 16px | 14px |
ds-input--40 | 40px | 0 16px | 14px |
ds-input--32 | 32px | 0 12px | 12px |
ds-input--28 | 28px | 0 12px | 12px |
ds-input--24 | 24px | 0 8px | 11px |
textarea는 위 height를 무시하고 항상 auto-height(min 4rem, padding8px 12px).
ds-select--XX
| 클래스 | height | font | padding-left | chevron |
|---|---|---|---|---|
ds-select--52 | 52px | 14px | 16px | 우 16px |
ds-select--48 | 48px | 14px | 16px | 우 16px |
ds-select--40 | 40px | 14px | 16px | 우 16px |
ds-select--32 | 32px | 12px | 16px | 우 16px |
ds-select--28 | 28px | 12px | 12px | 우 10px |
ds-select--24 | 24px | 11px | 8px | 우 8px |
스타일 변형: ds-select--fill-gray · ds-select--fill-white · ds-select--submenu(11px).
어떤 사이즈를 고르나 — 포인터 vs 손가락
스케일 안에서 고르는 기본 기준은 입력 장치입니다. 강제 매핑이 아니라 근거 있는 출발점이니, 화면이 다른 걸 요구하면 다른 값을 고르고 이유를 남기세요.
| 맥락 | 권장 사이즈 | 왜 |
|---|---|---|
| 데스크톱 폼·모달·필터 | ds-input--40 / ds-select--40 (조밀한 툴바는 ds-input--32) | 마우스 포인터 기준. 32가 요소 기본값 |
| 모바일 화면 · 모바일 풀페이지 모달 · 직원 앱 | ds-input--48 / ds-select--48 | 손가락 타깃. 40은 포인터 기준이라 오탭이 난다 |
| 표 안 인라인 편집 · 마이크로 그리드 | ds-input--28 / ds-input--24 | 행 높이에 맞춤 |
사이즈는 항상 이 클래스로 지정하세요. 픽셀을 직접 쓰면(Tailwind h-12 등) 높이만 먹고 보더·패딩·폰트가 함께 오지 않습니다.
폼 컨트롤은 숫자 표기(
--24/28/32/40/48/52, 숫자가 곧 높이 px)이고, 버튼은 영문 표기(ds-btn--xs/sm/md/ml/lg/xl)입니다. 높이가 같아도 클래스 이름은 계열마다 다릅니다.
⚠️ 글자 크기는 아직 미해결입니다. 사이즈 클래스가 정한 글자는 14px인데, iOS Safari는 16px 미만인 입력창을 탭하면 화면을 자동 확대합니다. 즉 지금 모바일 웹에서 입력칸을 탭하면 확대가 뜹니다(알고 두는 상태). 16px로 올리는 것도, viewport로 확대를 막는 것도 각각 대가가 있어 아직 규칙을 정하지 않았습니다 — 임의로 정하지 말고 Modal → iOS 자동 확대의 정리를 먼저 읽으세요.
같은 화면에서 두 스케일을 오가는 법(마크업은 데스크톱 기준, 모바일 구간에서만 덮기)은 → Modal → 모바일.
상태
| 상태 | 적용 | 스타일 |
|---|---|---|
| hover | :hover:not(:disabled) | border --ds-textfield-border-hover (#c1c1c1) |
| focus | :focus | border --ds-textfield-border-focus (#1b1b1b), select은 focus ring |
| disabled | :disabled | opacity: 0.45, cursor: not-allowed, bg alt |
| error | .ds-input--error / .ds-select--error | border --ds-color-danger (+ focus 시 ring) |
숫자 입력 (ds-input--number)
<input type="number">용 변형. 네이티브 증감 화살표(스피너)를 평소엔 숨기고 hover/focus 시에만 노출합니다 — 평소엔 가운데 정렬 숫자가 화살표에 밀리지 않고 깔끔하게, 조작 시엔 step 단위 증감(▲▼)이 가능. 숫자 정렬용 tabular-nums도 포함합니다. (키보드 ↑/↓는 화살표 노출과 무관하게 항상 동작.)
조합형 변형 — 이 클래스는 숫자 고유 처리(스피너 + tabular-nums)만 담당합니다. 크기는
ds-input--XX, 너비는w-full, 가운데 정렬은text-center유틸과 함께 씁니다(정렬은 선택 — 왼쪽 정렬 금액 입력 등엔 빼면 됨).
| 항목 | 처리 |
|---|---|
| 스피너 (기본) | 숨김 — webkit ::-webkit-*-spin-button { appearance: none } + Firefox appearance: textfield |
| 스피너 (hover/focus) | 노출 — appearance: auto로 ▲▼ 복귀(주로 webkit에서 동작). :not(:disabled) 가드 |
| 숫자 정렬 | font-variant-numeric: tabular-nums — 세로로 쌓인 숫자 칸의 자리수 정렬 |
<input type="number" x-model.number="wardSettingsForm.ratioDay" min="1" step="0.5"
class="ds-input--40 ds-input--number w-full text-center">라이브 미리보기는 상단 라이브 미리보기의 플레이그라운드에서 숫자 토글로 확인하세요.
흩어진 핵을 대체 — 그동안 숫자 인풋 스피너는 앱마다 제각각이었습니다(admin
.spinner-on-hover,.patient-count-input·.sched-grid input[type=number]항상 숨김, team-manager 인라인style="-moz-appearance:textfield").ds-input--number는 그중 검증된 hover/focus 노출 동작(admin.spinner-on-hover)을 공유 DS로 승격한 것으로, 앞으로 숫자 인풋은 이 단일 기준으로 통일합니다.
체크박스 · 라디오
둘은 한 세트예요. 크기(16px)·테두리·선택 색·hover·focus·disabled 값이 완전히 같고, 모양만 다릅니다 — 체크박스는 사각(radius 2px) + 흰 체크, 라디오는 원형 + 가운데 흰 점. 한 화면에 섞여 나와도 어긋나지 않게 값을 공유해요.
.ds-checkbox.ds-radio<label class="flex items-center gap-1.5">
<input type="checkbox" class="ds-checkbox" x-model="form.shared" /> 공개
</label>
<label class="flex items-center gap-1.5">
<input type="radio" class="ds-radio" name="rowBasis" :checked="basis === 'date'" @change="basis = 'date'" /> 날짜 기준
</label>| 클래스 | 크기 | 비고 |
|---|---|---|
ds-checkbox | 16px | 사각 2px · 선택 시 검정 채움 + 흰 체크 |
ds-checkbox--lg | 20px | 손가락 타깃이 필요한 자리(메신저 그룹 선택 등) |
ds-radio | 16px | 원형(링 12px) · 선택 시 검정 채움 + 흰 점 4px |
ds-radio--lg | 20px | 링 16px · 점 6px |
클래스를 빼먹으면 OS 파랑이 나옵니다
class를 안 붙인 <input type="checkbox">/<input type="radio">는 브라우저 기본 컨트롤로 렌더돼요. 선택 색이 macOS/Windows의 파랑이라 DS 팔레트 밖 색이 화면에 섞입니다. 입력 요소 자체는 DS가 :not([type="checkbox"]):not([type="radio"])로 의도적으로 제외하고 있어서, 클래스 없이는 아무것도 적용되지 않아요.
선택지에 설명이 붙는 경우 (2줄 조합)
선택지마다 "무엇인지"를 한 줄 더 설명해야 하면 라벨을 제목 + 보조설명 2줄로 쌓아요. 연차 정책처럼 고르는 순간 계산 규칙이 바뀌는 설정에서는 제목만으로 판단이 안 서기 때문이에요.
<div class="space-y-1.5">
<label class="flex items-start gap-2.5 cursor-pointer">
<input type="radio" name="policy" class="ds-radio ds-radio--lg mt-0.5 shrink-0" />
<span class="min-w-0 flex-1">
<span class="block text-b5 text-label-normal">공휴일을 연차에서 제외</span>
<span class="block text-b6 text-label-muted mt-0.5">5인 이상 사업장은 공휴일이 법정 유급휴일이에요</span>
</span>
</label>
</div>| 값 | 클래스 | 왜 |
|---|---|---|
| 컨트롤 크기 | --lg (20px) — 라디오·체크박스 공통 | 선택지 한 칸이 2줄(≈36px)이라 16px 기본은 글자 덩어리에 눌려 작아 보여요. 한 줄짜리는 기본 16px 그대로 |
| 컨트롤 정렬 | items-start + mt-0.5 (제목 크기와 무관하게 항상) | 설명이 붙어 라벨이 2줄이 되므로 가운데가 아니라 첫 줄에 맞춰요(items-center면 두 줄 사이로 내려가 어긋나요). 오프셋 2px 은 광학 보정이에요 — 글자는 줄 상자 안에서 아래로 치우쳐 앉기 때문에, 컨트롤을 줄 상자 위에 딱 맞추면 글자보다 위에 뜬 것처럼 보입니다. 계산상 높이가 같아도(14px 제목 ≈ 20px 컨트롤) 빼지 마세요 |
| 컨트롤 ↔ 글자 | gap-2.5 (10px) | 한 줄짜리(6px)보다 넓혀요 — 글자 덩어리가 커진 만큼 |
| 제목 / 설명 | text-b5 (13) / text-b6 (12) text-label-muted · mt-0.5 | 필드 라벨·설명과 같은 축 |
| 선택지 간격 | space-y-1.5 (6px) | 한 항목이 2줄이라 항목 사이가 줄 간격과 구분돼야 해요 |
| 글자 묶음 | min-w-0 flex-1 | 없으면 긴 설명이 컨트롤을 밀어냅니다 |
- 설명이 없는 선택지에는 쓰지 마세요 — 한 줄이면 기본 조합으로 충분해요.
- 선택지마다 카드 테두리가 필요할 만큼 설명이 길고 이미지·아이콘이 붙는다면 라디오가 아니라 선택 카드예요.
- 체크박스도 같은 조합을 씁니다(다중 선택 + 항목별 설명) — 크기도
ds-checkbox--lg로 맞춰요. 한 화면에 둘이 섞여 나올 때 한쪽만 16px 이면 바로 티가 납니다.
체크박스 vs 라디오 — 무엇을 쓰나
여러 개를 동시에 켤 수 있으면 체크박스, 여럿 중 하나만 고르면 라디오예요. 선택지가 2개이고 서로 반대말이면(공개/비공개) 라디오 2개보다 체크박스 1개가 낫습니다. 선택지가 5개를 넘어가면 라디오 대신 드롭다운을 고려하세요. 선택지마다 설명 한 줄이 붙어야 하면 선택 카드가 맞습니다.
스위치 (.ds-switch)
기능을 켜고 끄는 컨트롤이에요. 체크박스와 달리 누른 즉시 반영되는 자리에 씁니다 — 저장 버튼을 눌러야 적용되는 폼 값에는 체크박스를, "출근 현황 겹쳐보기"처럼 켜자마자 화면이 바뀌는 자리에는 스위치를 쓰세요.
.ds-switch 32×20--sm 26×16<!-- 라벨을 <label>로 감싸면 글자를 눌러도 토글돼요 -->
<label class="flex items-center gap-2">
<span class="text-b6 text-neutral-600">출근 현황</span>
<input type="checkbox" class="ds-switch ds-switch--sm"
:checked="showAttendance" @change="toggleAttendance()"
aria-label="출근 현황 토글">
</label>| 클래스 | 트랙 | 손잡이 | 쓰는 곳 |
|---|---|---|---|
ds-switch | 32×20 | 16px | 기본 — 설정 목록, 모달 안 옵션 행 |
ds-switch ds-switch--sm | 26×16 | 12px | 툴바처럼 세로 공간이 빠듯한 줄 |
- 켜짐은 primary 주황, 꺼짐은
neutral-300(hoverneutral-400), disabled는 45% 불투명도예요. 색을 따로 지정하지 마세요. <input type="checkbox">에만 붙습니다.<button>이나<div>로 스위치 모양을 직접 그리지 마세요 — 키보드 조작과 스크린리더가 공짜로 따라옵니다.- 글자 라벨이 옆에 없으면
aria-label이 필수예요.
스위치 · 체크박스 · 토글 버튼 — 어떤 걸 쓰나
사용된 토큰
| 토큰 | 값 | 용도 |
|---|---|---|
--ds-textfield-border | #e5e5e5 | input/textarea 기본 테두리 |
--ds-textfield-border-hover | #c1c1c1 | hover 테두리 |
--ds-textfield-border-focus | #1b1b1b | focus 테두리 |
--ds-textfield-radius | 4px | 필드 radius (중앙 조정점) |
--ds-textfield-fg / --ds-textfield-placeholder | #1b1b1b / #8f8f8f | 글자 / placeholder |
--ds-dropdown-bg/border/text/radius | #fff / #e5e5e5 / #1b1b1b / 4px | select 박스 |
--ds-dropdown-chevron | SVG data-uri (#8f8f8f stroke) | select 화살표 |
검토 필요 사항
- 🟡
--ds-textfield-*/--ds-dropdown-*가 raw hex로 정의 (primitive 미연결) —--color-neutral-100/-900등 primitive를var()로 참조하면 primitive 한 곳만 고쳐도 모든 입력칸에 전파되는 진짜 단일 출처가 됩니다. 단, 지금 단순 전환하면 안 되는 트레이드오프가 있습니다:--ds-*는 의도적으로 runtime-safe literal 세트(토큰 생성기가 hex만 export하고,@theme처리·tree-shaking에 의존하지 않게 둔 집합)입니다. 실측상 일부 앱 빌드에선 primitive가:root에서 가지치기됩니다(--color-label-white가 module-manager·inventory 빌드에서 제거됨) → barevar()는 그 앱들에서 배경이 깨지고, fallbackvar(--color-x, #hex)는 fallback hex가 사실상 사본이라 전파가 다시 끊깁니다. 올바른 해법 = primitive를 전 앱:root에 항상 emit(예:@theme static)하도록 정비한 뒤--ds-*를 일괄 연결 — DS 토큰 아키텍처 차원의 별도 작업으로 보류합니다(목표 & 로드맵).
적용 범위 & 도입 현황
이 컴포넌트는 DS에 등록된 변수(크기 24~52 · 상태 · 토큰)를 골라 쓰는 전역 폼 컨트롤입니다. 적용은 두 층으로 나뉩니다.
- 전역 기본 (자동 · 전 서비스) —
input/select/textarea요소 규칙이라, 클래스를 안 붙여도 모든 입력칸이 기본 32px 룩을 자동으로 받습니다. 마이그레이션과 무관하게 이미 전 서비스에 적용됨. - 명시적 사이즈 전환 (도입 진행 중) — 기본 32px가 아닌 크기가 필요한 곳에만
ds-input--XX/ds-select--XX를 부여. 궁극 목표는 전 서비스 정합이며, 현재 진행 상황은 아래와 같습니다.
| 시점 | 전환된 곳 | 적용 사이즈 |
|---|---|---|
| PR #247 | admin · general-affairs 폼 31개 (x-model 전량 보존) | --32 |
| 모달 마이그레이션 | 직원 추가/수정 위저드 · 근태 모달 (hr-system · admin) | --40 |
| 이후 | 잔여 모달 · 앱 전역 | 진행 예정 |
"사용처 목록"이 아니라 도입 진행 상황으로 기록합니다 — 기본 룩은 이미 전역이라, 추적할 가치가 있는 건 "기본과 다른 크기로 의도적으로 전환한 곳"뿐입니다. 전체로 퍼져도 이 표는 낡지 않습니다.
메뉴 패널 (ds-menu)
네이티브 select로 표현이 안 되는 커스텀 옵션 목록(트리·체크 표시·그룹 라벨/구분선)은 공유 컴포넌트 .ds-menu 패널을 씁니다. OS 기본 드롭다운 대신 직접 띄우는 플로팅 메뉴로, 실제 직원 수정 모달의 팀/역할 선택에 사용됩니다. (design-system.css에 등록된 공유 컴포넌트 — 라이브 미리보기는 상단 라이브 미리보기 참고)
| 클래스 | 역할 |
|---|---|
.ds-menu | 패널 컨테이너 (흰 바탕·라인·그림자·radius) |
.ds-menu--scrollable | 최대 12rem 높이 + 세로 스크롤 |
.ds-menu__item | 메뉴 항목 (button/a) |
.ds-menu__item--row | 좌우 배치 (체크 아이콘 등 우측 정렬) |
.ds-menu__item--selected | 선택 상태 — 30% 회색 틴트의 꽉 찬 사각 바 (Figma 13413:9425) |
.ds-menu__divider | 구분선 |
.ds-menu__label | 그룹 라벨 (10px, muted) |
.ds-menu__hint | 안내/빈 상태 텍스트 |
토큰: 배경·라인·radius는
--ds-dropdown-*, 선택 틴트는--ds-hover-tint-30, 구분선은--ds-line-normal—select와 같은 드롭다운 토큰 계열을 공유합니다.
ms-dropdown (앱 로컬 — 공유 DS 아님)
다중 선택용 .ms-dropdown(커스텀 트리거+패널)은 admin·team-manager의 src/styles/main.css에만 정의된 앱 로컬 패턴으로, 공유 design-system.css에는 없습니다. PR #247에서 연장근무 상태 select → ms-dropdown으로 교체. 트리거(.ms-dropdown__trigger)는 Tooltip의 compact 툴팁 대상이라 선택 라벨이 hover로 표시됩니다. 공유 DS로 끌어올릴지는 별도 검토 대상이며, **현재 공유 표준은 ds-menu**입니다.
피커 (Picker) — 날짜 · 월 · 시간
폼 컨트롤의 세 번째 패밀리. 텍스트 한 줄(Input)·선택지(Select)와 달리 날짜·월·시간을 고르는 입력입니다 — 날짜 = 커스텀 달력(dsDatePicker — 네이티브 type="date"는 대안), 월 = 커스텀 팝오버(.ds-menu), 시간 = 커스텀 드롭다운(dsTimePicker). 모달·페이지 공용이며, 라이브 조작은 상단 라이브 미리보기에서. 모달 안에서 쓸 때의 레이어·닫기 규칙은 Modal §7.
달력 (날짜 · 월 선택)
DS 정책 — 날짜는 커스텀 우선(시스템 기본은 대안), 월은 커스텀
- 날짜 선택 = 커스텀 달력(우선) — 결합 피커와 동일한 커스텀 달력(연도 드롭다운 + 월 이동 ‹ › + 삭제·오늘 + 오늘=채움·다른 날=테두리)을 씁니다. 앞으로 날짜 입력은 커스텀으로 통일 예정이며 커스텀을 우선합니다. 시스템 기본
<input type="date">는 대안으로 유지(네이티브 지원·모바일 휠·로케일이 필요하거나 아직 미전환인 화면). - 월 선택 = 커스텀 컴포넌트 — 네이티브
<input type="month">는 브라우저 지원이 약해(Firefox 등 미지원),.ds-menu팝오버 + 연도.ds-select+ 월 그리드의 커스텀 월 선택기를 씁니다.
라이브 미리보기는 상단 라이브 미리보기에서 직접 조작해 보세요 — 날짜(커스텀·우선): 연도 드롭다운 + 월 이동 + 삭제·오늘, 오늘=채움·다른 날=테두리. 날짜(시스템 기본·대안): 브라우저 네이티브 달력(OS/브라우저별 외형). 월: 커스텀 팝오버(트리거 아래 6px·우측정렬).
날짜 선택기 구성 (커스텀 — dsDatePicker)
네이티브 type="date"를 대체하는 공유 커스텀 달력. Alpine 컴포넌트 dsDatePicker(@hereby/components, createAuthComponentsPlugin에 등록 — 전 앱 사용 가능) + 공유 마크업 partial(packages/components/html/ds-date-picker.html) + .ds-datepicker* CSS(design-system.css)로 구성. 사용처는 호스트 div + include 한 줄이면 됩니다(로직·외형·마크업 모두 공유, 중복 없음).
| 파트 | 내용 |
|---|---|
| 트리거 | readonly input.ds-input--40(우측 달력 아이콘). 누르면 달력 열림 |
| 팝오버 | x-teleport="body"로 모달 밖에 렌더 + position:fixed로 트리거 바로 아래 배치 → 모달의 overflow-y-auto·filter를 탈출해 절대 안 잘림(모달 밖으로 넘어가도 OK). dim 없음, z = 모달 위 1레이어(--ds-z-modal-top) |
| 그리드 | 연도 드롭다운 + 월 이동 ‹ › + 일자. 선택&오늘 = 채움 / 선택&다른 날 = 테두리 |
| 푸터 | .ds-btn-text--sm — 삭제(assistive) · 오늘(primary) |
| 닫기 | Esc · 바깥 클릭 · 날짜 선택 → 피커만 닫힘(모달은 유지) |
| 값 | YYYY-MM-DD 문자열 — 호스트의 x-model에 x-modelable="value"로 바인딩 |
사용법 (사용처 마크업):
<div class="ds-datepicker" x-data="dsDatePicker({ max: '9999-12-31' })"
x-modelable="value" x-model="form.someDate">
<!-- include "../../packages/components/html/ds-date-picker.html" -->
</div>- 옵션:
dsDatePicker({ min, max, placeholder })—min/max는YYYY-MM-DD(범위 밖 날짜 비활성). - 컴팩트 트리거: 호스트에 사이즈 모디파이어를 더해 트리거 높이를 줄입니다(기본 40px).
ds-datepicker--32(32px) → 필터 줄의ds-select--32/ds-input--32와 정렬(예: 근태 현황 탭 기간 필터).ds-datepicker--sm(28px) → 더 좁은 인라인 행(예: 스케줄러 희망일 목록). - 비활성: 호스트에
x-effect="disabled = (조건)". - 선택 시 파생 로직(예: 신고일 → 차기기한 자동계산):
dsDatePicker({ onChange: () => fn() })— 유저 선택 시에만 발화(pick/clear/today, 다음 tick에 x-model 동기화 후). 네이티브@change와 동일하게 프로그램적 로드엔 안 뜸.
원본: completion-form.html · 컴포넌트 DsDatePicker.ts · 마크업 ds-date-picker.html.
날짜 범위 필터 (프리셋 버튼)
리스트/현황 화면의 기간(from ~ to) 필터는 dsDatePicker 2개(시작·종료) + 프리셋 버튼 줄로 구성합니다.
| 파트 | 마크업·동작 |
|---|---|
| 시작·종료 피커 | dsDatePicker 2개, 둘 다 ds-datepicker--32(필터 줄 높이 정렬). 교차 clamp — 시작에 `x-effect="max = (to |
| 프리셋 버튼 | 이번주 · 지난주 · 이번달 · 지난달 · 최근 3개월 | 전체. 각 버튼 text-c1(11px)·rounded-lg, 활성 하이라이트는 공유 datePresetBtnClass(active, key)(활성=진한 라인 / 비활성=회색 라인) |
| 초기화 버튼 | ds-btn ds-btn--icon ds-btn--muted ds-btn--sm(새로고침 아이콘, title="기간 초기화"). `:disabled="!(from |
- 동작: 프리셋 클릭 →
resolveDatePresetRange(preset)가from/to를 계산하고 활성 key(certDatePreset/docMgmtDatePreset등 상태 필드 1개)를 저장 후 재조회. 피커에서 날짜를 직접 고르면onChange에서 프리셋 key를 비워(수동 선택 = 프리셋 해제) 즉시 재조회. 초기화는from/to/프리셋을 함께 비움. resolveDatePresetRange·datePresetBtnClass는 앱 공유 헬퍼 — 새 필터도 이 둘을 재사용하세요(프리셋 세트·하이라이트가 자동 일치).
원본: 증명서 필터 documents.html (setCertDatePreset) · 문서관리 document-management.html (setDocMgmtDatePreset).
월 선택기 구성 (커스텀)
| 파트 | 마크업 |
|---|---|
| 트리거 | 필드 모양 div(role="button", h-32 · --ds-textfield-radius · 달력 아이콘). select가 아니라 div라 전역 버튼/인풋 규칙과 충돌 없음 |
| 팝오버 표면 | .ds-menu (240px, padding 12px) — 트리거 우측 정렬(right-0), top-full + 마진 6px(모달 밖 넘침 방지) |
| 연도 | .ds-select.ds-select--32 (현재 −10년 ~ +1년, 12개) |
| 월 그리드 | 4×3 버튼. 선택 --color-label-normal(검정 채움) · 이번 달 --color-label-normal 테두리 · 기본 --color-line-normal 테두리 |
| 푸터 | .ds-btn-text--sm — 닫기(assistive) · 이번 달(primary) |
원본: apps/admin/src/views/modals/employee-attendance.html (mpOpen / mpYear / setAttendanceMonth).
왜 커스텀을 우선하나: 날짜+시간 결합 피커가 커스텀 달력을 쓰는 이상, 단독 날짜도 같은 커스텀으로 맞춰야 외형·동작이 일관됩니다(연도 드롭다운·삭제·오늘·선택 강조 등 제품이 통제). 그래서 앞으로 커스텀으로 통일하고 커스텀을 우선합니다. 단 네이티브
type="date"도 대안으로 유지 — 접근성·모바일 휠·로케일이 공짜이고, 아직 전환 안 된 화면은 네이티브 그대로 둡니다. (전환은 점진적; 신규/시각통일 필요 화면부터 커스텀)
시간 입력 (Time input)
DS 정책 — 시각은 기본 네이티브, 안내형 입력은 커스텀 드롭다운
- 기본 = 네이티브
<input type="time">— 단순 시각 입력은 네이티브를 씁니다(ds-input--40 tabular-nums). DS 테두리·높이 자동, 모바일 휠 공짜. 예: 근태 수정 모달. - 커스텀 시간 드롭다운(
dsTimePicker) — 안내형 12시간 입력(오전/오후 + 시/분 + 증감 스텝 + 유효성)이 필요할 때. 트리거를 누르면 인풋 아래 드롭다운이 뜹니다(날짜 피커와 동일 —x-teleport모달 위·안 잘림·dim 없음). 근무 설정의 시작/종료시간이 이 패턴.
라이브 미리보기는 상단 라이브 미리보기에서 직접 — 17 입력 → 오후 5시 자동 변환, ▲▼ 스텝(시 1↔12·분 0↔59 랩), 숫자 아님/무변경이면 확인 비활성.
구성 (커스텀 — dsTimePicker)
날짜 피커와 같은 형태의 공유 컴포넌트 — Alpine 팩토리 dsTimePicker + 공유 마크업 partial(packages/components/html/ds-time-picker.html) + .ds-timepicker* CSS. 사용처는 호스트 div + include 한 줄:
<div class="ds-timepicker" x-data="dsTimePicker()" x-modelable="value" x-model="form.startTime">
<!-- include "../../packages/components/html/ds-time-picker.html" -->
</div>| 파트 | 마크업 |
|---|---|
| 트리거 | readonly input.ds-input--40(우측 시계 아이콘·tabular-nums) — 누르면 드롭다운 오픈. 클릭 트리거라 OS time 피커 대신 커스텀 뷰가 뜸 |
| 드롭다운 | x-teleport="body" + position:fixed로 트리거 바로 아래 → 모달 overflow·filter 탈출해 안 잘림, dim 없음, z = 모달 위 1레이어(--ds-z-modal-top). Esc·바깥클릭·취소 = 피커만 닫음(모달 유지) |
| 오전/오후 | .ds-select--40 드롭다운(오전/오후) — 단일선택이라 드롭다운 |
| 시 · 분 | .ds-input--40(가운데정렬·tabular-nums·키보드 입력) + 위·아래 스텝 아이콘 버튼(간격 6px). 시 1↔12·분 0↔59 랩어라운드 |
| 푸터 | .ds-btn--solid --white-bk(취소) · .ds-btn--solid --dark(확인) --md. 확인은 유효 + 변경 시에만 활성 |
값 변환: 표시 = 12시간제(오전/오후 + 시 + 분), 저장 = 24시간제 HH:MM — 호스트의 x-model에 바인딩. 시 칸은 24시간제 입력도 허용해(17 → 오후 5시) blur 시 정규화 — 자정 = 오전 12, 정오 = 오후 12. 확인 활성 조건 = 시(0–23)·분(0–59) 순수 숫자 AND 원래 값에서 변경.
원본: shift-form.html(시작/종료) · 컴포넌트 DsTimePicker.ts · 마크업 ds-time-picker.html.
날짜 + 시간 결합
결합 뷰 = 한 팝업에 [달력 + 시간]. 네이티브 <input type="datetime-local">은 OS·브라우저마다 외형/동작이 달라(공지 만료일시가 이 케이스) 쓰지 않고, 그 "한 view 결합" 경험을 DS로 재현합니다 — 트리거 클릭 → 한 팝업에 커스텀 달력 그리드(좌) + 커스텀 시간 폼(우, 오전·오후 + 시·분 + 스텝) → 확인 시 YYYY-MM-DD HH:mm 저장.
⚠️ 결합 뷰는 달력 그리드를 시간 폼과 한 팝업에 인라인으로 그립니다 — 네이티브 시스템 달력은 브라우저 자체 팝업이라 한 팝업에 못 넣기 때문. 단일 날짜는 같은 커스텀 달력을
dsDatePicker드롭다운으로 띄웁니다(외형·동작 일관). 즉 커스텀 달력 그리드는 단독·결합 모두 쓰며, 차이는 단독=teleport 드롭다운 / 결합=한 팝업 인라인입니다.
라이브는 상단 라이브 미리보기에서.
마이그레이션 대상: 현재
datetime-local을 쓰는 곳(예: 공지 작성 만료일시)은 이 조합(시스템 달력 + 커스텀 시간 폼)으로 교체 권장.
목표 & 로드맵
| 항목 | 현재 | 목표 |
|---|---|---|
| 기본 32px 통일 | ✅ (40→32) | 유지 |
| input/select 사이즈 스케일 정합 | ✅ 52~24 동일 | 유지 |
| 사이즈 모디파이어 강제 제거 | ✅ !important 없음 | 유지 |
| 시맨틱 토큰 → primitive 연결 | 🟡 runtime-safe literal (의도적) | primitive를 전 앱 :root에 항상 emit(@theme static 등)한 뒤 일괄 연결 — 별도 DS 작업 |
| chevron 색 토큰화 | 🟡 data-uri 한계 (값은 on-palette #8f8f8f=neutral-400) | 빌드타임 색 주입(생성 단계)만 가능 — <select>는 pseudo-element 불가라 mask-image 안 됨. 보류(ROI 낮음) |
변경 이력
| 날짜 | 내용 |
|---|---|
| 2026-07-07 | 날짜 범위 필터(프리셋 버튼) 패턴 신설(#date-range-filter) — 리스트 기간 필터를 dsDatePicker 2개(시작·종료, x-effect 교차 clamp) + 프리셋 줄(이번주/지난주/이번달/지난달/최근 3개월 | 전체) + --icon --muted 초기화 버튼으로 문서화. 프리셋 계산·하이라이트는 공유 헬퍼 resolveDatePresetRange/datePresetBtnClass 재사용. 증명서 필터(admin)·문서관리(team-manager) 도입 (design/adjust-table) |
| 2026-06-30 | 필드 구성(anatomy) 섹션 신설 — 설명·에러 표기 표준화 — 텍스트 필드의 라벨→필수→입력칸→설명→에러 스택을 필드 구성으로 문서화(라이브 <DemoFieldAnatomy>). 설명 줄 = 새 공유 클래스 .ds-form-help(12px·아래 6px·항상 표시, 색은 필드 상태 추종 rest muted→focus black→error red, sibling 조합자로 JS 없이 동작) — 그동안 text-b6 neutral-400/500 mt-0.5~1.5로 제각각이던 걸 단일 표준으로 고정. 에러 메시지 = .ds-form-error/.ds-form-error-slot을 admin main.css 로컬 → 공유 design-system.css로 승격(danger·12px·아래 6px, slot은 min-height:17px로 점프 방지). 입력칸 보더는 admin .is-error가 아니라 공유 ds-input--error 표준으로 명시 |
| 2026-06-26 | 숫자 입력 변형(ds-input--number) 신설 — <input type=number>용 공유 변형. 네이티브 스피너를 평소 숨기고 hover/focus 시에만 노출(admin .spinner-on-hover 동작을 공유 DS로 승격) + tabular-nums. 크기·너비·정렬(ds-input--XX·w-full·text-center)과 조합. 숫자 입력 섹션·라이브 미리보기(<DemoFormControls> 숫자 토글) 추가, 흩어진 patient-count-input·sched-grid·인라인 -moz-appearance 핵을 대체할 단일 기준으로 명시. 도입: scheduler ward-settings 비율 입력(admin·tm) |
| 2026-06-26 | 시간 입력 피커 = teleport 드롭다운으로 전환(dsTimePicker) — 기존 dim 중앙 다이얼로그(.ds-modal-backdrop--top + .ds-mini-modal)를 날짜 피커와 동일한 인풋 아래 드롭다운(x-teleport·fixed·안 잘림·dim 없음·Esc/바깥클릭=피커만 닫힘)으로. te* 인라인 로직을 공유 dsTimePicker(팩토리+partial+.ds-timepicker* CSS)로 추출 → shift-form(시작/종료)·notice-form(만료시각) 적용. #time-input "구성" 갱신, ## 피커 인트로 "시간=다이얼로그"→"드롭다운" |
| 2026-06-26 | 커스텀 날짜 달력 컴포넌트(dsDatePicker) 신설 + 앱 적용 — 네이티브 type=date 대체 공유 컴포넌트(Alpine DsDatePicker + 공유 마크업 partial + .ds-datepicker* CSS)를 "날짜 선택기 구성"으로 문서화. x-teleport="body" + fixed 드롭다운이라 모달 overflow·filter 탈출해 안 잘림(dim 없음·모달 위 1레이어·Esc/바깥클릭=피커만 닫힘). 사용처는 호스트 div + include 한 줄. 데스크톱 폼 12필드 전수 적용(ga·admin·hr·inventory·tm) + 좁은 행용 --sm(28px) 변형. 정정: ## 피커 인트로 "날짜=네이티브"→"커스텀 달력", #datetime의 "단일 날짜=네이티브(커스텀 안 만듦)" 모순 제거(단독=드롭다운/결합=인라인) |
| 2026-06-25 | Input · Select · Picker 3패밀리로 정리 — 제목에 Picker 추가, ## 피커 (Picker) 상위 섹션 신설하고 달력(날짜·월)·시간 입력을 그 아래로 그룹화(#calendar/#time-input 앵커 유지). 그리고 "시간 입력 (Time input)" 신설 — 커스텀 시간 다이얼로그(오전/오후 드롭다운 + 시/분 키보드 입력 + ▲▼ 스텝 + 17→오후5 정규화 + 숫자아님/무변경 시 확인 비활성) 라이브(<DemoTimeInput>)·구성·값변환·날짜+시간 결합 정책. 기본=네이티브 type=time, 안내형=커스텀 다이얼로그(ds-mini-modal). 날짜+시간 결합 라이브(<DemoDateTime>, {#datetime}) 추가 — 결합 뷰가 라이브 예시 없던 것 보완 |
| 2026-06-19 | 라이브 미리보기 = 컴포넌트 갤러리로 확장(폼 컨트롤·메뉴 패널·달력 3종을 상단에 모으고 각 항목 설명 더보기 앵커 → 상세 섹션) · 메뉴 패널(ds-menu) 섹션 신설(__item--selected 포함) · "달력(날짜·월 선택)" 섹션 신설(날짜=시스템 기본 type=date, 월=커스텀 월 선택기 — <DemoDatePicker> 라이브) · "사용처" → "적용 범위 & 도입 현황"으로 재구성(모달 마이그레이션 반영) · ms-dropdown=앱 로컬 정정 · 미리보기에 textarea·드롭다운 fill 변형 추가 · 검토 필요 사항 정리 — ① primitive 연결 항목을 runtime-safe literal 트레이드오프로 보강(보류), ②chevron(on-palette·data-uri 한계)·③24px(일관 literal px·이슈 아님)·④!important-free(정상)는 검토 완료로 제거 |
| 2026-06-08 | Form controls 페이지 신규 작성 + 라이브 미리보기(<DemoFormControls>) (PR #247 기준) |
| PR #247 | 폼 필드·박스 버튼 기본 40→32px 통일 · ds-input 사이즈 스케일(28/40/48/52) 추가 · admin+general-affairs 31개 폼 ds-select/ds-input 마이그레이션 · select chevron 렌더 버그 수정 |