Skip to content

UI v0.19.1 레이아웃및토큰체계정비 - #35

Merged
youngduck merged 7 commits into
mainfrom
ui-v0.19.1-레이아웃및토큰체계정비
Jul 14, 2026

Hidden character warning

The head ref may contain hidden characters: "ui-v0.19.1-\ub808\uc774\uc544\uc6c3\ubc0f\ud1a0\ud070\uccb4\uacc4\uc815\ube44"
Merged

UI v0.19.1 레이아웃및토큰체계정비#35
youngduck merged 7 commits into
mainfrom
ui-v0.19.1-레이아웃및토큰체계정비

Conversation

@youngduck

Copy link
Copy Markdown
Owner

Branch: ui-v0.19.1-레이아웃및토큰체계정비

추가

  • feat: 기능색상램프토큰추가
  • feat: spacing토큰및레이아웃컴포넌트추가
  • feat: Card컴포넌트variant추가

변경

  • refactor: Tabs디자인개편및구분선토큰계층정비
  • refactor: Overview쇼케이스및파운데이션타이포토큰정비
  • chore: v0.19.1버전범프

youngduck and others added 7 commits July 14, 2026 16:42
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
yd-ui Ready Ready Preview, Comment Jul 14, 2026 8:25am

@youngduck
youngduck merged commit 01ab330 into main Jul 14, 2026
4 of 6 checks passed
@youngduck

Copy link
Copy Markdown
Owner Author

@gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the design system to version 0.19.1, introducing new layout components (Stack, Inline, and Field) and reorganizing the token system. The Card component now supports outlined and filled variants, while the Tabs component has been simplified by removing the animated indicator. New foundations for borders, colors, and spacing scales have also been added. The review feedback suggests enhancing the accessibility of the new Field component by using React.cloneElement to automatically inject aria-describedby and aria-invalid attributes into its child input element.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/components/Layouts/Field/Field.tsx

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new set of layout components (Stack, Inline, and Field) along with their corresponding design tokens, styles, stories, and documentation. It also refactors the Card component to support outlined and filled variants, and simplifies the Tabs component by removing the sliding indicator animation in favor of a simpler border and text-color highlight. A review comment highlights a critical issue in the new Field component, where wrapping the entire structure in a <label> tag violates HTML standards when block-level children are passed, causes unintended focus behaviors, and lacks proper accessibility associations (such as aria-describedby). Refactoring the container to a <div> and programmatically injecting accessibility attributes into the child input is recommended.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +7 to +49
import { useId } from 'react'

type FieldProps = {
/** 입력 위에 표시되는 라벨 */
label: string
/** 입력 아래 보조 설명 (에러가 있으면 에러가 우선 표시) */
description?: string
/** 에러 메시지. 지정 시 설명 대신 표시 */
error?: string
/** 필수 입력 표시(*) 여부 */
required?: boolean
/** 라벨이 감쌀 입력 요소 */
children: React.ReactNode
} & Omit<React.ComponentPropsWithoutRef<'label'>, 'children'>

export function Field({ label, description, error, required, children, className = '', ...props }: FieldProps) {
const messageId = useId()

return (
<label className={`yds-field ${className}`} {...props}>
<span className="yds-field-label">
{label}
{required && (
<span className="yds-field-required" aria-hidden="true">
*
</span>
)}
</span>
{children}
{error ? (
<span id={messageId} className="yds-field-error" role="alert">
{error}
</span>
) : (
description && (
<span id={messageId} className="yds-field-description">
{description}
</span>
)
)}
</label>
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Field 컴포넌트가 전체 영역을 <label> 태그로 감싸고 있어 다음과 같은 잠재적인 UX 및 웹 표준 문제가 발생할 수 있습니다:

  1. HTML 표준 위반: <label> 태그 내부에는 <div>와 같은 블록 레벨 요소(Flow content)를 배치할 수 없으며, 오직 구문 콘텐츠(Phrasing content)만 포함할 수 있습니다. DatePickerSelectBox 등 내부적으로 <div>를 사용하는 복잡한 컴포넌트가 children으로 들어올 경우 HTML 표준에 위배됩니다.
  2. 의도치 않은 포커스/클릭 동작: <label> 내부의 어느 곳(설명 텍스트, 에러 메시지 등)을 클릭하더라도 브라우저는 내부의 첫 번째 포커스 가능한 요소(input)로 포커스를 강제 이동시킵니다. 이로 인해 커스텀 드롭다운이나 달력 팝업이 열리자마자 닫히거나, 보조 버튼 클릭이 무시되는 등의 오동작이 발생할 수 있습니다.
  3. 접근성(Accessibility) 누락: 에러 메시지나 설명글에 id={messageId}를 부여하고 있으나, 정작 children으로 전달되는 input 요소에는 aria-describedby가 연결되지 않아 스크린 리더가 설명이나 에러를 읽지 못합니다.

개선 제안

컨테이너를 <div>로 변경하고, <label>을 별도로 분리하여 htmlFor로 연결하는 구조를 권장합니다. 또한, React.cloneElement를 활용하여 children으로 들어오는 단일 input 요소에 idaria-describedby, aria-invalid 속성을 안전하게 자동 주입해 주면 접근성과 사용성을 모두 확보할 수 있습니다.

import { useId, Children, isValidElement, cloneElement }
from 'react'

type FieldProps = {
  /** 입력 위에 표시되는 라벨 */
  label: string
  /** 입력 아래 보조 설명 (에러가 있으면 에러가 우선 표시) */
  description?: string
  /** 에러 메시지. 지정 시 설명 대신 표시 */
  error?: string
  /** 필수 입력 표시(*) 여부 */
  required?: boolean
  /** 라벨이 감쌀 입력 요소 */
  children: React.ReactNode
} & React.ComponentPropsWithoutRef<'div'>

export function Field({ label, description, error, required, children, className = '', ...props }: FieldProps) {
  const uniqueId = useId()
  const inputId = uniqueId + "-input"
  const messageId = uniqueId + "-message"

  const renderChildren = () => {
    try {
      const child = Children.only(children)
      if (isValidElement(child)) {
        return cloneElement(child as React.ReactElement<any>, {
          id: child.props.id || inputId,
          'aria-describedby': error || description ? messageId : undefined,
          'aria-invalid': error ? true : undefined,
        })
      }
    } catch {
      // children이 단일 요소가 아니거나 유효하지 않은 경우 그대로 반환
    }
    return children
  }

  return (
    <div className={"yds-field " + className} {...props}>
      <label htmlFor={inputId} className="yds-field-label">
        {label}
        {required && (
          <span className="yds-field-required" aria-hidden="true">
            *
          </span>
        )}
      </label>
      {renderChildren()}
      {error ? (
        <span id={messageId} className="yds-field-error" role="alert">
          {error}
        </span>
      ) : (
        description && (
          <span id={messageId} className="yds-field-description">
            {description}
          </span>
        )
      )}
    </div>
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant