fix: align disabled input label opacity with ui_viz

This commit is contained in:
DESKTOP-KPCP6AL\ㅅㅎ committed 2026-10-11 12:45:22 +09:00
1 parent 4cfe224123
commit 91c8e5e14e
5 files changed
+86 -5

No files matched your search

+14
View File
@@ -91,6 +91,14 @@ native `<input>` 동작 전체(`type`, `file:` 스타일 class, `placeholder`),
## 10. 사용자 확정 결정
**2026-10-11 최종 사용자 결정 — Disabled Input Label**
- Input Story 소비 조합(`src/components/input.stories.tsx`의 `Specimen`)의 Disabled Label은 기존 `text-muted-foreground` → `--muted-foreground` → `--hm-sem-fg-secondary` 색(`#6E6759`)을 유지하고, `input.disabled`일 때만 `opacity-60`을 적용한다. HEX·Semantic Token·공개 Input API는 변경하지 않는다.
- Disabled Input 컨트롤 `0.6`, 소비 Label `0.6`, 부모 Field `1`이다. 형제 요소에 각각 적용하여 중첩 opacity를 만들지 않는다. 기본·readOnly·Error Label은 기존 `1`을 유지한다(Disabled가 아닌 경우).
- 공통 [Label Component](label.md#4-상태-규격)의 Disabled `0.5`는 유지한다. Helper Text(`#6E6759`)와 Error Message(`#D32F2F`)도 기존 구현을 유지하며 이번 변경 대상이 아니다.
- 이전 검토의 “Label 100% 유지”는 당시 선택이며 현재 최종 결정은 Disabled Input Story Label `60%`다. 아래 2026-10-02 승인과 2026-10-06 검증은 당시 기록으로 보존하며, 이번 Label 정책의 현재 검증을 대신하지 않는다.
- 실제 소비 화면은 자동으로 확대 수정하지 않는다. 신규 화면의 공개 Label 재사용·control 연결 계약은 유지하며, 공통 Label의 Disabled 정책을 이 Story 결정으로 대체하지 않는다.
**2026-10-02**
- Input Disabled opacity `0.6` 확정.
@@ -131,3 +139,9 @@ src/components/story-board.tsx
- `npm run typecheck`, `npm run build`, `npm run build-storybook` 통과. 기존 문서 예시 CSS warning 및 Storybook chunk 크기 / plugin timing warning 유지.
- 검증 범위: Chromium 환경. 다른 브라우저의 렌더링은 별도 확인이 필요하다.
- 대표 `All`과 숨김 `InvalidFocus` 확인. 일반 Focus는 border만 변경, Small/Medium/Large 28/34/42px 및 12/14/16px 유지. Readonly border 0 / padding 0, Disabled opacity 0.6 유지. Border CSS 계약 1.5px, 이 브라우저 환경의 computed border-width는 1px으로 정규화; padding 보정은 11.5px 유지. 기존 non-invalid Readonly의 UA outline 정책은 이번 범위에서 유지.
### Disabled Input Label 검증 (2026-10-11)
- 실제 Storybook(Chromium/Edge): Disabled Label `rgb(110, 103, 89)` / opacity `0.6`, Input `0.6`, 부모와 모든 조상 opacity `1`. 공통 Label Disabled `0.5`, Input Default/readOnly/Error Label `1` 유지.
- 실제 배경 `rgb(245, 240, 229)`에서 합성색 약 `#A49E91`, 대비 약 `2.35:1`(원본 100% 약 `4.93:1`). 일반 텍스트 `4.5:1`보다 낮으며, 실제 disabled 컨트롤에 연결된 비활성 UI 라벨의 WCAG 1.4.3 예외와 구분한다. 사용자 확정 정책은 유지하고 가독성 우려를 기록한다.
- Default/Disabled/Readonly/Error/Error Message/Playground 및 대표 All 390/768/1280/1440px, `htmlFor`/control 연결·편집·readOnly·disabled focus·문서 overflow 확인. Typecheck, UI build, Storybook build 통과. Guard FAIL 0, 원본 `NOT VERIFIED` 보존. [검증 범위와 제한](../reports/disabled-input-label-2026-10-11.md).
+3 -2
View File
@@ -93,12 +93,12 @@ Radix Label 동작·접근성, `data-slot`, `cn(..., className)`, `select-none`,
## 10. 사용자 확정 결정
- 추가 사용자 결정 없음.
- **2026-10-11 최종 사용자 결정**: [Input Story 소비 조합의 Disabled Label](input.md#10-사용자-확정-결정)은 원본 색 `#6E6759` / opacity `0.6`을 적용한다. 공통 Label Component의 Disabled `0.5`는 그대로 유지한다. Input 기본·readOnly·Error Label은 Disabled가 아니면 `1`, Message / Helper Text는 기존 구현 유지다. 이 결정은 공통 Label API·Theme·Checkbox/Switch/Radio Label 규칙으로 확대하지 않는다.
## 11. 미확정 / 보류
- 독립 Label 규격 없음. Input·Textarea Field Label 값을 Label 컴포넌트에 적용한 것이다. Checkbox/Radio option label의 전경 role은 ui_viz `TBD`(`field-semantics.md` §3)이므로 해당 컴포넌트 작업에서 따로 확인한다.
- Disabled opacity: Input의 "Label·Message 포함 .6"(2026-10-02 Input 한정 결정)은 확대하지 않고 shadcn 기본 0.5를 유지.
- Disabled opacity의 과거 기록: Input의 "Label·Message 포함 .6"(2026-10-02 Input 한정 결정)은 공통 Label로 확대하지 않고 shadcn 기본 `0.5`를 유지했다. 2026-10-11 최종 결정은 Input Story 소비 Label만 `0.6`으로 적용하며, Message는 변경하지 않는다. 이전 검토의 “Label 100% 유지”와 과거 검증은 당시 기록이고 현재 정책은 §10을 따른다.
- Required 표시 색(`#D32F2F`, TEXTAREA-07)과 표시 방식은 소비 조합이다. 컴포넌트 API 없음.
- D-093 원본 실행본 13/700: `KNOWN_DEVIATION / FOLLOW_UP`.
@@ -118,3 +118,4 @@ src/components/label.stories.tsx
- Storybook computed style(`components-form-label--all`): 14px / 20px / 500, color `rgb(110,103,89)`, Disabled 연결 시 opacity 0.5.
- 라벨 클릭 시 연결된 input으로 실제 focus 이동 확인.
- 2026-10-02 당시 미확인: 사이드바 화면, 390px 폭, Checkbox/Radio와의 조합. 현재 Checkbox/Radio는 구현되어 있으나 이 과거 검증 기록이 조합 검증 완료를 뜻하지는 않는다.
- 2026-10-11 선택 회귀: 실제 공통 Label 대표 Story Disabled의 색 `rgb(110, 103, 89)` / opacity `0.5`, 부모 `1`, disabled Input과의 `htmlFor` 연결 확인. 공통 Label 구현·Theme는 변경하지 않았다. [Input Story Label 60% 별도 결정 및 검증](../reports/disabled-input-label-2026-10-11.md).
+1
View File
@@ -194,6 +194,7 @@ HM 지정값이 있는 색상, radius, control height, typography 등은 Theme T
- 소비자는 `variant`/`size` 같은 API만 고르고 HM 전용 class를 붙이지 않는다. `className`은 확장용이다.
- ui_viz가 `Confirmed`로 정의한 상태 표현은 그 범위에서 shadcn 기본 시각 스타일을 대체할 수 있다. ui_viz에 정의가 없거나 미확정인 동작과 상태(예: Button의 Focus는 전역 `TBD`, `aria-invalid`는 정의 없음)는 shadcn 기본 구현을 유지하며 HM이 임의로 만들지 않는다.
- ui_viz가 컴포넌트 안에 Field(Label, Required 별표, Helper/Error 문구)를 포함해도 shadcn 컴포넌트에 wrapper를 추가하지 않는다. Input은 입력 컨트롤만 담당하고 Label·문구는 소비 조합이다. Story와 Demo는 일반 요소에 shadcn Token 유틸(`text-muted-foreground`, `text-destructive`)과 ui_viz 수치(Label 14/20/500, 문구 12/18 또는 Error 11/18, field gap 8)를 맞춰 조합한다. Field/Label 컴포넌트 도입은 별도 결정이다.
- **2026-10-11 최종 결정**: 기존 Input Story 소비 조합의 Disabled Label만 원본 `#6E6759` / opacity `0.6`을 적용한다. Input 컨트롤 `0.6`, 공통 Label Component Disabled `0.5`, Input 기본·readOnly·Error Label `1`(Disabled가 아닐 때)을 유지한다. 부모 Field에 opacity를 적용하거나 Message / Helper Text·전역 Token으로 확대하지 않는다. 이전 “Label 100% 유지” 검토 및 2026-10-02/06 기록은 당시 근거로 보존한다. 상세 기준은 [Input 사용자 결정](../components/input.md#10-사용자-확정-결정)을 따른다. 신규 업무 화면의 공개 Label 재사용 계약은 그대로 유지한다.
- ui_viz prop 이름이 native HTML 속성과 겹치면(Input `size`) ui_viz 이름을 쓰고 native 속성을 대체한다. 단 native 속성이 CSS 폭과 무관해 쓰임이 없을 때만 그렇게 한다. Input은 `size="small" | "medium" | "large"`이고 생략하면 ui_viz 기본값 Large(42px)다. 기본값은 컴포넌트마다 ui_viz 문서를 따르므로 Button의 `default`(= Medium)와 같다고 가정하지 않는다.
- 상태는 컴포넌트에 이미 있는 native selector(Input: `:focus-visible`, `[aria-invalid="true"]`, `:disabled`, `[readonly]`)로 적용하고 새 `data-state`를 만들지 않는다. ui_viz가 "shadow·glow 없음"을 Confirmed로 정의한 상태(Input Focus·Error)는 TSX에서 shadcn ring class를 빼고 CSS에서 `box-shadow: none`을 선언한다. ui_viz에 정의가 없어 shadcn ring을 유지하는 Button과 구분한다.
- ui_viz에서 `Temporary`인 값은 구현하지 않고 shadcn 기본값을 유지한다. 같은 상태에서 Confirmed인 값(Input Disabled 면 White)만 덮어쓴다. 단 Input Disabled opacity `.6`은 ui_viz에서는 `Temporary`이지만, **2026-10-02 사용자 결정**(Select 작업에서 확정된 Disabled 시각 intent와의 정합)으로 Input과 Select에 한해 `0.6`으로 확정되었다. 이전의 "`.6` Temporary → 미적용" 판단은 이 결정으로 superseded 되었다. 전역 Disabled opacity Token으로 승격하지 않으며, Button 등 다른 컴포넌트는 각자의 Confirmed 규격 또는 별도 사용자 결정을 따른다.
@@ -0,0 +1,64 @@
# Disabled Input Label 최종 결정 및 선택 검증 — 2026-10-11
RESULT: PARTIAL — 요청한 디자인 변경과 선택 실행 검증은 PASS. Guard 원본의 NOT VERIFIED 3건과 해당 스타일 Runtime/Final 미검증은 보존한다. 이 결과는 전체 디자인 시스템 또는 WCAG 적합성 인증이 아니다.
범위 `STORYBOOK_ONLY`, Runtime React. 공식 프로젝트 `D:\디자인시스템\HMDesign-Gitea-Transfer`, `main`, origin `https://gitea.hmac.kr/b25027/HMDesign.git`. 시작 Local/Remote HEAD는 모두 `4cfe224123b3a12633a90be569ae41b7c2aac5df`, Working Tree clean. `ui_viz`는 읽기 전용 비교에만 사용했다.
## 최종 정책과 실제 렌더
기존 Input Story `Specimen`의 native Label에만 `input.disabled` 조건으로 `opacity-60`을 추가했다. native Label은 이번 사용자 요청의 기존 fixture를 보존하는 선택이며 공개 Label을 신규 화면에서 대체하도록 승인하는 것이 아니다. 라벨은 인스턴스별 `useId` → `htmlFor` → 실제 Input ID로 연결되며 브라우저의 `label.control`과 접근성 이름을 확인했다.
| 항목 | Computed Style / 결과 |
|---|---|
| Disabled Input Label | `rgb(110, 103, 89)` (`#6E6759`), opacity `0.6` |
| Disabled Input | opacity `0.6` 유지 |
| 공통 Label Component Disabled | opacity `0.5` 유지, 실제 대표 Label Story에서 확인 |
| Input 기본 / readOnly / Error Label | opacity `1` (disabled 아닌 상태) |
| 부모 Field / 모든 조상 | opacity `1`, 중첩 opacity 없음 |
| Error Message | `rgb(211, 47, 47)` / opacity `1`, 기존 구현 유지 |
| Helper Text | 기존 `#6E6759` / opacity `1` 구현 유지, 관련 과거 실행 증거 REUSED PASS |
| Theme / Semantic / 공개 API / 전역 CSS | 변경 없음 |
readOnly와 disabled를 동시에 준 Playground는 disabled 조건을 따라 Label `0.6`이다. readOnly 단독 Label `1`과 구분한다. Message와 Helper Text는 이번 변경 대상이 아니다. 기존 2026-10-02 승인, 2026-10-06 및 2026-10-07 검증은 삭제하지 않았다. 이전 “Label 100% 유지” 검토는 당시 선택이며 현재 [최종 결정](../components/input.md#10-사용자-확정-결정)은 60%다. [공통 Label 계약](../components/label.md#4-상태-규격)은 50%를 유지한다.
## 접근성
- 실제 Label 배경 `#F5F0E5`, 전경 `#6E6759`, opacity `0.6`을 sRGB 채널별 합성한 값은 `(164, 157.8, 145)` → 약 `#A49E91`이다. 대비율은 비반올림 합성 RGB 기준 `2.34955:1`, 8-bit 반올림 기준 `2.34548:1`. 원본 100% 대비율은 `4.93094:1`이다.
- 일반 크기 텍스트의 AA `4.5:1` 기준보다 낮다. [W3C WCAG 2.2 SC 1.4.3](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)은 비활성 UI의 일부인 텍스트를 대비 요구에서 제외한다. 실제 disabled Input에 의미적으로 연결된 이번 Label은 해당 예외로 평가하며, 일반 안내 텍스트·활성 컨트롤·readOnly에 예외를 확대하지 않는다.
- 예외 가능성은 가독성 보장이 아니다. 흐린 항목명의 가독성 우려를 남기며, 사용자 확정 디자인을 임의로 100%로 되돌리지 않는다. 스크린리더 발화·다른 브라우저·실기기 터치는 NOT VERIFIED.
## 선택 검증
| 검증 | 결과 / 범위 |
|---|---|
| Diff 및 요청 범위 | Input Story 조건부 Label class와 직접 관련 설명·MD만 변경 |
| 실제 Storybook / Computed Style | PASS, Default/Disabled/Readonly/Error/Error Message/Playground |
| Playground 분기 | PASS, disabled/readOnly/aria-invalid 및 disabled+readOnly |
| 대표 All | PASS, 390/768/1280/1440px 각 상태 색·opacity·연결·줄바꿈·중복 ID·문서 overflow |
| 상호작용 | PASS, Default Label 클릭 focus·입력, readOnly 값 보존, Disabled Label 클릭 후 컨트롤 비포커스 |
| Typecheck | PASS, `npm run typecheck` (사전 package build 포함) |
| UI Build | PASS, typecheck 사전 `npm run build:packages`의 UI build를 재사용; 반복하지 않음 |
| Storybook Build | PASS, `npm exec --no -- storybook build -o _local/disabled-input-label-20261011/storybook --disable-telemetry` |
| 변경 문서 링크 | PASS, Input/Label MD·프로젝트 지침·본 보고서 4개 문서, 내부 링크 20개, 오류 0 |
| Helper Text | REUSED PASS, 기존 `_local/final-consistency-20261011/helper-runtime.json`의 Input Playground Default/Disabled/Error 메시지 분기. 이번 Diff에서 메시지 class·부모 opacity·연결·Theme 모두 불변 |
| Checkbox / Switch / Radio | 공유 Label의 peer/group Disabled `0.5` 구현 불변과 관련 컴포넌트·Theme 무변경을 확인. 각 조합의 새 브라우저 전수 검증은 실행하지 않음 |
브라우저 실행 증거는 `_local/disabled-input-label-20261011/runtime.json`(35개 선택 확인, 페이지 오류 0)과 상태별·폭별 PNG다. Default는 자동화 재개 시 이미 통과한 동일 산출물 증거를 재사용했다. 캡처에서 390px 대표 All과 Disabled 상태를 시각 확인했다. 패키지 build와 Storybook build의 기존 chunk 크기·plugin timing 경고는 비차단이다. 기존 `storybook-static` 파일 쓰기 권한 오류는 새 검증 출력 폴더로 해결했고 저장소 설정은 바꾸지 않았다. `_local` 자료는 Git 배포에 포함하지 않으며 위 표는 당시 관측의 보존 기록이다.
### AI Guard 원본
```sh
node scripts/ai-generation-guard.mjs --target . --profile STORYBOOK --scope PARTIAL --entry src/components/input.stories.tsx --files src/components/input.stories.tsx --json
```
- FAIL 0, 원본 `status`, `staticStatus`, `runtimeStatus` 모두 `NOT VERIFIED`.
- `REUSE-004`: 기존 Input fixture native Label, 이번 작업의 보존 사유와 실제 control 연결은 위에 별도 기록.
- `TOKEN-002`: 기존 `story-board.tsx:19`의 동적 style, `story-page.tsx:13`의 동적 class. 기존 구조를 이번 범위에서 수정하지 않는다.
- 원본 `styleEvidence`: STATIC `PASS`, RUNTIME `NOT VERIFIED`, DESIGN_INTENT `PASS`, FINAL `NOT VERIFIED`. 별도 결합 `styleVerification`은 생성하지 않았다. 실제 변경 Label의 색·opacity와 관련 상태 Runtime 측정은 PASS지만 기존 Guard의 모든 style 위치를 검증했다는 뜻은 아니다.
- Guard JSON을 PASS로 덮어쓰지 않았으며 동일 정상 검사를 반복하지 않았다. Storybook profile에는 Production 전용 `--result`를 전달하지 않는다.
## 영향 범위와 Git
`src/work-dashboard/WorkDashboard.tsx`의 진행률 Input은 `editor.status === "done"`에 따라 disabled이며 별도 공개 Label을 조합한다. 해당 Label은 이번 Story fixture 적용 대상이 아니므로 추가 opacity를 넣지 않았다. 실제 소비 화면의 추가 적용은 별도 판단 대상이다. 공통 Label·Input 구현, Theme, 다른 Story·Component·Pattern에는 변경이 없다.
공식 Gitea `main`의 일반 Commit/Push 메시지는 `fix: align disabled input label opacity with ui_viz`를 사용한다. 관련 파일만 Stage하고 Staged Diff를 검토하며 직전 원격 SHA가 시작 기준과 같을 때만 진행한다. 실제 최종 Commit/Push 및 Local/Remote SHA 검증 결과는 작업 완료 보고에 기록한다.
+4 -3
View File
@@ -25,7 +25,7 @@ function Specimen({
const messageId = `${fieldId}-message`
return (
<div className="grid gap-2">
<label htmlFor={fieldId} className="justify-self-start text-sm font-medium text-muted-foreground">
<label htmlFor={fieldId} className={`justify-self-start text-sm font-medium text-muted-foreground${input.disabled ? " opacity-60" : ""}`}>
{label}
{required ? (
<span aria-hidden="true" className="text-destructive">
@@ -103,7 +103,7 @@ const meta = {
docs: {
description: {
component:
"ui_viz docs/components/input.md: Small 28 / Medium 34 / Large 42px, size를 생략하면 Large. Focus와 Error는 1.5px 테두리로 구분하며 shadow·glow를 쓰지 않습니다. Hover는 없습니다. Label·Helper·Error 문구는 Input이 아니라 소비 조합입니다. Disabled는 흰 면을 유지하고 opacity 0.6으로 흐립니다(2026-10-02 사용자 결정, Input·Select 한정).",
"ui_viz docs/components/input.md: Small 28 / Medium 34 / Large 42px, size를 생략하면 Large. Focus와 Error는 1.5px 테두리로 구분하며 shadow·glow를 쓰지 않습니다. Hover는 없습니다. Label·Helper·Error 문구는 Input이 아니라 소비 조합입니다. Disabled 컨트롤은 흰 면과 opacity 0.6을 유지합니다(2026-10-02 사용자 결정). 이 Story 소비 조합의 Disabled Label도 0.6입니다(2026-10-11 최종 사용자 결정). 기본·Readonly·Error Label은 1, 공통 Label Component Disabled는 기존 0.5이며 Helper·Message는 변경하지 않습니다.",
},
},
},
@@ -136,7 +136,7 @@ export const All: Story = {
<StorySection title="Sizes" description="Small 28 / Medium 34 / Large 42px. 업무 폼·조회 조합은 Medium을 씁니다.">
<SizeComparison />
</StorySection>
<StorySection title="States" description="Focus와 Error는 1.5px 테두리로 구분하며 Hover는 없습니다. Disabled는 흰 면을 유지하고 흐리게 표시합니다.">
<StorySection title="States" description="Focus와 Error는 1.5px 테두리로 구분하며 Hover는 없습니다. Disabled는 흰 면을 유지하고 컨트롤과 소비 Label 각각 60%로 표시합니다. Readonly Label은 100%입니다.">
<StateBoard
states={[
{ id: "focus", label: "Focus", children: <Specimen size="medium" placeholder={placeholder} /> },
@@ -194,6 +194,7 @@ export const Required: Story = {
export const Disabled: Story = {
tags: ["verification"],
render: () => narrow(<Specimen disabled defaultValue="Unavailable project" />),
parameters: { docs: { description: { story: "2026-10-11 최종 사용자 결정: Input과 이 소비 조합의 Label 각각 opacity 0.6, 부모는 1. Label 원본 색은 #6E6759이며 공통 Label Component의 Disabled 0.5와 구분합니다. Helper·Message는 이번 변경 대상이 아닙니다." } } },
}
export const Readonly: Story = {
tags: ["verification"],