{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "type": "registry:ui",
  "title": "Field",
  "description": "입력 컴포넌트에 라벨과 도움말, 오류 메시지, 글자 수를 붙여 하나의 완결된 입력 단위로 만드는 래퍼입니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/icons",
    "@atom/lib-bem",
    "@atom/lib-cn",
    "@atom/lib-context",
    "@atom/lib-field-context"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/field.tsx",
      "content": "\"use client\";\n\nimport {\n  Children,\n  Fragment,\n  type HTMLAttributes,\n  type LabelHTMLAttributes,\n  type MouseEvent,\n  type ReactNode,\n  type Ref,\n  isValidElement,\n  useCallback,\n  useId,\n  useLayoutEffect,\n  useMemo,\n  useState,\n} from \"react\";\nimport { mod } from \"../lib/bem\";\nimport { cn } from \"../lib/cn\";\nimport { createStrictContext } from \"../lib/context\";\nimport { FieldControlProvider } from \"../lib/field-context\";\nimport { AlertIcon } from \"./icons\";\n\n/* -------------------------------------------------------------------------- */\n/*  Field (components/field.md, styles/atom/elements/field.css)               */\n/*    <Field label=\"휴대폰 번호\" required description=\"숫자만\" count={{ current: 0, max: 11 }}> */\n/*      <TextInput />                                                         */\n/*    </Field>                                                                */\n/*                                                                            */\n/*    <Field invalid>                                   // compound           */\n/*      <Field.Header><Field.Label>사용 기간</Field.Label></Field.Header>     */\n/*      <Field.Control>                                                       */\n/*        <DateInput aria-label=\"시작일\" /><Field.Separator>~</Field.Separator> */\n/*        <DateInput aria-label=\"종료일\" />                                   */\n/*      </Field.Control>                                                      */\n/*      <Field.Footer><Field.Error>종료일이 시작일보다 빨라요</Field.Error></Field.Footer> */\n/*    </Field>                                                                */\n/*                                                                            */\n/*  Control 슬롯의 컨트롤은 useFieldControl() 로 id · aria-describedby ·      */\n/*  aria-invalid · disabled · required 를 받아 갑니다 (lib/field-context).    */\n/* -------------------------------------------------------------------------- */\n\nexport type FieldLayout = \"vertical\" | \"horizontal\";\nexport type FieldSize = \"small\" | \"medium\";\nexport type FieldCount = { current: number; max: number };\n\nconst B = \"atom-field\";\n\ntype Registered = { description: boolean; error: boolean; optional: boolean };\n\ntype FieldContextValue = {\n  controlId: string;\n  labelId: string;\n  descriptionId: string;\n  errorId: string;\n  optionalId: string;\n  required: boolean;\n  invalid: boolean;\n  hasError: boolean;\n  register: (part: keyof Registered, on: boolean) => void;\n};\n\nconst [FieldContext, useFieldContext] = createStrictContext<FieldContextValue>(\"Field\");\n\n/** 파트가 마운트돼 있는 동안 Root 에 존재를 알립니다 (aria-describedby 조합용). */\nfunction useRegister(ctx: FieldContextValue, part: keyof Registered, on: boolean) {\n  const { register } = ctx;\n  useLayoutEffect(() => {\n    if (!on) return;\n    register(part, true);\n    return () => register(part, false);\n  }, [register, part, on]);\n}\n\nexport type FieldProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n  ref?: Ref<HTMLDivElement>;\n  /** 입력 칸의 이름입니다. 생략하면 compound 파트(Field.Header · Field.Control · Field.Footer)로 직접 구성합니다. */\n  label?: ReactNode;\n  /** 라벨 뒤에 `*` 를 붙입니다. */\n  required?: boolean;\n  /** 선택 항목임을 알리는 텍스트입니다. 예: `\"선택\"` */\n  optionalText?: string;\n  /** 컨트롤 아래 도움말입니다. */\n  description?: ReactNode;\n  /** `invalid` 일 때 description 을 대신합니다. */\n  errorMessage?: ReactNode;\n  /** 오류 상태로 바꿉니다. */\n  invalid?: boolean;\n  /** 라벨까지 포함해 전체를 흐리게 만듭니다. */\n  disabled?: boolean;\n  /** 라벨의 위치를 결정합니다. */\n  layout?: FieldLayout;\n  /** 라벨과 푸터의 폰트 크기입니다. */\n  size?: FieldSize;\n  /** 글자 수 카운터를 노출합니다. */\n  count?: FieldCount;\n  /** 컨트롤의 `id` 와 연결합니다 (Label sub-asset). 생략하면 자동으로 만듭니다. */\n  htmlFor?: string;\n  /** 감쌀 컨트롤입니다. 하나여도 되고, Button 이나 `__separator` 를 포함한 조합이어도 됩니다. */\n  children: ReactNode;\n};\n\nfunction FieldRoot({\n  ref,\n  label,\n  required = false,\n  optionalText,\n  description,\n  errorMessage,\n  invalid = false,\n  disabled = false,\n  layout = \"vertical\",\n  size = \"medium\",\n  count,\n  htmlFor,\n  className,\n  children,\n  ...rest\n}: FieldProps) {\n  const base = useId();\n  const controlId = htmlFor ?? `${base}-control`;\n  const labelId = `${base}-label`;\n  const descriptionId = `${base}-description`;\n  const errorId = `${base}-error`;\n  const optionalId = `${base}-optional`;\n\n  const [parts, setParts] = useState<Registered>({ description: false, error: false, optional: false });\n  const register = useCallback((part: keyof Registered, on: boolean) => {\n    setParts((prev) => (prev[part] === on ? prev : { ...prev, [part]: on }));\n  }, []);\n\n  const hasError = invalid && parts.error;\n  const describedBy =\n    [parts.optional && optionalId, hasError ? errorId : parts.description && descriptionId].filter(Boolean).join(\" \") ||\n    undefined;\n\n  const ctx = useMemo<FieldContextValue>(\n    () => ({ controlId, labelId, descriptionId, errorId, optionalId, required, invalid, hasError, register }),\n    [controlId, labelId, descriptionId, errorId, optionalId, required, invalid, hasError, register],\n  );\n  const control = useMemo(\n    () => ({ controlId, labelId, describedBy, invalid, disabled, required, size }),\n    [controlId, labelId, describedBy, invalid, disabled, required, size],\n  );\n\n  // label prop 이 있으면 원본 Props 대로 Header · Control · Footer 를 그립니다.\n  let content: ReactNode;\n  if (label !== undefined) {\n    const showFooter =\n      description !== undefined || (invalid && errorMessage !== undefined) || count !== undefined;\n    content = (\n      <>\n        <FieldHeader>\n          <FieldLabel>{label}</FieldLabel>\n          {optionalText !== undefined && <FieldOptional>{optionalText}</FieldOptional>}\n        </FieldHeader>\n        <FieldControl>{children}</FieldControl>\n        {showFooter && (\n          <FieldFooter>\n            {description !== undefined && <FieldDescription>{description}</FieldDescription>}\n            {errorMessage !== undefined && <FieldError>{errorMessage}</FieldError>}\n            {count !== undefined && (\n              <FieldCounter>\n                {count.current}/{count.max}\n              </FieldCounter>\n            )}\n          </FieldFooter>\n        )}\n      </>\n    );\n  } else {\n    content = children;\n  }\n\n  // horizontal: Header 를 뺀 나머지를 __body 로 감쌉니다 (field.css --layout_horizontal).\n  if (layout === \"horizontal\") {\n    const head: ReactNode[] = [];\n    const body: ReactNode[] = [];\n    const flatten = (node: ReactNode) => {\n      Children.forEach(node, (child) => {\n        if (isValidElement<{ children?: ReactNode }>(child) && child.type === Fragment) {\n          flatten(child.props.children);\n        } else if (isValidElement(child) && child.type === FieldHeader) head.push(child);\n        else body.push(child);\n      });\n    };\n    flatten(content);\n    content = (\n      <>\n        {head}\n        <div className={`${B}__body`}>{body}</div>\n      </>\n    );\n  }\n\n  return (\n    <FieldContext value={ctx}>\n      <FieldControlProvider value={control}>\n        <div\n          {...rest}\n          ref={ref}\n          data-invalid={invalid ? \"\" : undefined}\n          data-disabled={disabled ? \"\" : undefined}\n          className={cn(B, mod(B, \"layout\", layout, \"vertical\"), mod(B, \"size\", size, \"medium\"), className)}\n        >\n          {content}\n        </div>\n      </FieldControlProvider>\n    </FieldContext>\n  );\n}\n\n/** 라벨 · 필수/선택 표시를 담는 가로 줄 (`__header`). */\nfunction FieldHeader({ ref, className, ...rest }: HTMLAttributes<HTMLDivElement> & { ref?: Ref<HTMLDivElement> }) {\n  useFieldContext(\"Field.Header\");\n  return <div {...rest} ref={ref} className={cn(`${B}__header`, className)} />;\n}\n\n/** 라벨 요소에 연결할 수 있는(labelable) 요소인지 — 아니면 클릭 시 직접 포커스를 옮깁니다. */\nconst LABELABLE = new Set([\"INPUT\", \"SELECT\", \"TEXTAREA\", \"BUTTON\", \"METER\", \"OUTPUT\", \"PROGRESS\"]);\n\n/**\n * 입력 칸의 이름 (`<label class=\"atom-field__label\">`). `for` 는 Control 의 id 와 자동으로 연결되고,\n * Field 가 `required` 면 `aria-hidden` 인 `*` 를 붙입니다.\n */\nfunction FieldLabel({\n  ref,\n  className,\n  children,\n  htmlFor,\n  onClick,\n  ...rest\n}: LabelHTMLAttributes<HTMLLabelElement> & { ref?: Ref<HTMLLabelElement> }) {\n  const ctx = useFieldContext(\"Field.Label\");\n  const target = htmlFor ?? ctx.controlId;\n  return (\n    <label\n      {...rest}\n      ref={ref}\n      id={ctx.labelId}\n      htmlFor={target}\n      className={cn(`${B}__label`, className)}\n      onClick={(e: MouseEvent<HTMLLabelElement>) => {\n        onClick?.(e);\n        // role=\"combobox\" 인 div 처럼 labelable 이 아닌 컨트롤은 브라우저가 포커스를 옮겨주지 않습니다.\n        const el = typeof document !== \"undefined\" ? document.getElementById(target) : null;\n        if (el && !LABELABLE.has(el.tagName)) el.focus();\n      }}\n    >\n      {children}\n      {ctx.required && (\n        <span className={`${B}__required`} aria-hidden=\"true\">\n          *\n        </span>\n      )}\n    </label>\n  );\n}\n\n/** 선택 항목 표시 (`__optional`). 정보이므로 숨기지 않고 컨트롤의 aria-describedby 로 연결됩니다. */\nfunction FieldOptional({ className, ...rest }: HTMLAttributes<HTMLSpanElement>) {\n  const ctx = useFieldContext(\"Field.Optional\");\n  useRegister(ctx, \"optional\", true);\n  return <span {...rest} id={ctx.optionalId} className={cn(`${B}__optional`, className)} />;\n}\n\n/** Control 슬롯 (`__control`). 입력 하나, 입력 + Button, 입력 + Separator + 입력 조합이 모두 같은 슬롯입니다. */\nfunction FieldControl({ ref, className, ...rest }: HTMLAttributes<HTMLDivElement> & { ref?: Ref<HTMLDivElement> }) {\n  useFieldContext(\"Field.Control\");\n  return <div {...rest} ref={ref} className={cn(`${B}__control`, className)} />;\n}\n\n/** 범위 입력의 두 입력 사이 텍스트 (`__separator`, `aria-hidden`). */\nfunction FieldSeparator({ className, children = \"~\", ...rest }: HTMLAttributes<HTMLSpanElement>) {\n  useFieldContext(\"Field.Separator\");\n  return (\n    <span {...rest} aria-hidden=\"true\" className={cn(`${B}__separator`, className)}>\n      {children}\n    </span>\n  );\n}\n\n/** Description(또는 Error)과 Counter 를 한 줄에 담는 Footer (`__footer`). */\nfunction FieldFooter({ ref, className, ...rest }: HTMLAttributes<HTMLDivElement> & { ref?: Ref<HTMLDivElement> }) {\n  useFieldContext(\"Field.Footer\");\n  return <div {...rest} ref={ref} className={cn(`${B}__footer`, className)} />;\n}\n\n/** 도움말 (`__description`). 오류 상태에서 Field.Error 가 있으면 자리를 내줍니다. */\nfunction FieldDescription({ className, ...rest }: HTMLAttributes<HTMLSpanElement>) {\n  const ctx = useFieldContext(\"Field.Description\");\n  const hidden = ctx.hasError;\n  useRegister(ctx, \"description\", !hidden);\n  if (hidden) return null;\n  return <span {...rest} id={ctx.descriptionId} className={cn(`${B}__description`, className)} />;\n}\n\n/** 오류 메시지 (`__error`, `role=\"alert\"`). Field 가 `invalid` 일 때만 렌더되고 Description 을 대신합니다. */\nfunction FieldError({ className, children, ...rest }: HTMLAttributes<HTMLParagraphElement>) {\n  const ctx = useFieldContext(\"Field.Error\");\n  useRegister(ctx, \"error\", ctx.invalid);\n  if (!ctx.invalid) return null;\n  return (\n    <p {...rest} id={ctx.errorId} role=\"alert\" className={cn(`${B}__error`, className)}>\n      <AlertIcon className={`${B}__error-icon`} />\n      {children}\n    </p>\n  );\n}\n\n/** 글자 수 (`__counter`). 바뀔 때마다 읽히지 않도록 aria-live 를 걸지 않습니다. */\nfunction FieldCounter({ className, ...rest }: HTMLAttributes<HTMLSpanElement>) {\n  useFieldContext(\"Field.Counter\");\n  return <span {...rest} className={cn(`${B}__counter`, className)} />;\n}\n\nexport const Field = Object.assign(FieldRoot, {\n  Header: FieldHeader,\n  Label: FieldLabel,\n  Optional: FieldOptional,\n  Control: FieldControl,\n  Separator: FieldSeparator,\n  Footer: FieldFooter,\n  Description: FieldDescription,\n  Error: FieldError,\n  Counter: FieldCounter,\n});\n",
      "type": "registry:ui",
      "target": "components/ui/field.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/button.css",
      "content": "/* ============================================================================\n   Element — Button\n   ----------------------------------------------------------------------------\n   atom-button\n     --size_{xsmall|small|medium|large}      medium is the default\n     --variant_{brand-solid|neutral-solid|neutral-weak|critical-solid\n                |brand-outline|neutral-outline|ghost}\n     --layout_icon-only\n     --width_full\n   states: :hover  [data-pressed]  [disabled]  [data-loading]\n\n   Loaded by both the card preview and the detail page, so\n   this file is the only definition of the component.\n   ========================================================================== */\n\n.atom-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: var(--atom-x2);\n  height: 47px;\n  padding-inline: var(--atom-x5);\n  border-radius: var(--atom-radius-r2);\n  font-size: 15px;\n  font-weight: 600;\n  line-height: 1;\n  white-space: nowrap;\n  user-select: none;\n  transition: filter var(--atom-duration-fast) ease,\n    background-color var(--atom-duration-fast) ease;\n}\n\n.atom-button__icon {\n  width: 18px;\n  height: 18px;\n  flex-shrink: 0;\n}\n\n.atom-button__label {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* --- variants ------------------------------------------------------------- */\n.atom-button--variant_neutral-solid {\n  background: var(--atom-bg-neutral-solid);\n  color: var(--atom-fg-on-solid);\n}\n\n.atom-button--variant_brand-solid {\n  background: var(--atom-primary-600);\n  color: #fff;\n}\n\n.atom-button--variant_critical-solid {\n  background: var(--atom-bg-critical-solid);\n  color: #fff;\n}\n\n.atom-button--variant_neutral-weak {\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-button--variant_brand-outline {\n  background: transparent;\n  color: var(--atom-primary-600);\n  box-shadow: inset 0 0 0 1px currentColor;\n}\n\n.atom-button--variant_neutral-outline {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n  box-shadow: inset 0 0 0 1px var(--atom-border-default);\n}\n\n.atom-button--variant_ghost {\n  background: transparent;\n  color: var(--atom-fg-neutral);\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-button--size_xsmall {\n  height: 32px;\n  gap: var(--atom-x1);\n  padding-inline: var(--atom-x3);\n  border-radius: var(--atom-radius-full);\n  font-size: 13px;\n}\n\n.atom-button--size_xsmall .atom-button__icon {\n  width: 14px;\n  height: 14px;\n}\n\n.atom-button--size_small {\n  height: 40px;\n  padding-inline: var(--atom-x4);\n  font-size: 14px;\n}\n\n.atom-button--size_small .atom-button__icon {\n  width: 16px;\n  height: 16px;\n}\n\n.atom-button--size_large {\n  height: 54px;\n  padding-inline: var(--atom-x6);\n  border-radius: var(--atom-radius-r3);\n  font-size: 16px;\n}\n\n.atom-button--size_large .atom-button__icon {\n  width: 20px;\n  height: 20px;\n}\n\n/* --- layout -------------------------------------------------------------- */\n.atom-button--layout_icon-only {\n  width: 47px;\n  padding-inline: 0;\n}\n\n.atom-button--layout_icon-only.atom-button--size_xsmall {\n  width: 32px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_small {\n  width: 40px;\n}\n\n.atom-button--layout_icon-only.atom-button--size_large {\n  width: 54px;\n}\n\n.atom-button--width_full {\n  width: 100%;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-button:hover:not([disabled]):not([data-loading]) {\n  filter: brightness(0.94);\n}\n\n.atom-button--variant_ghost:hover:not([disabled]),\n.atom-button--variant_neutral-outline:hover:not([disabled]),\n.atom-button--variant_brand-outline:hover:not([disabled]) {\n  filter: none;\n  background: var(--atom-bg-neutral-weak);\n}\n\n.atom-button[data-pressed] {\n  filter: brightness(0.88);\n}\n\n.atom-button[disabled] {\n  opacity: 0.35;\n  cursor: not-allowed;\n  filter: none;\n}\n\n.atom-button[data-loading] {\n  cursor: progress;\n}\n\n.atom-button[data-loading] .atom-button__label {\n  opacity: 0.45;\n}\n\n.atom-button__spinner {\n  width: 16px;\n  height: 16px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-full);\n  border: 2px solid currentColor;\n  border-top-color: transparent;\n  animation: atom-button-spin 700ms linear infinite;\n}\n\n@keyframes atom-button-spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/button.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/field.css",
      "content": "/* ============================================================================\n   Element — Field\n   ----------------------------------------------------------------------------\n   The label / description / error / counter wrapper every input-ish component\n   is dropped into. Field owns the surrounding text, never the control itself.\n\n   atom-field\n     --layout_horizontal                     label to the left of the control\n     --size_{small|medium}                   medium is the default\n     [data-invalid]  [data-disabled]\n     __header  __label  __required  __optional\n     __control                               slot for the actual control\n     __separator                             the text between a range's inputs\n     __footer  __description  __error  __counter\n\n   __control is a row, not a single box. One input, an input plus a Button, or\n   two inputs joined by __separator for a range — all of them are the same slot,\n   so the 4px rhythm and the single row height are defined here once instead of\n   in every page that needs a combination.\n   ========================================================================== */\n\n.atom-field {\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n  width: 100%;\n  min-width: 0;\n}\n\n.atom-field__header {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n}\n\n.atom-field__label {\n  font-size: 13px;\n  font-weight: 600;\n  line-height: 1.4;\n  color: var(--atom-fg-neutral);\n}\n\n.atom-field__required {\n  margin-left: 2px;\n  color: var(--atom-primary-600);\n}\n\n.atom-field__optional {\n  font-size: 12px;\n  font-weight: 400;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n/* --- control slot -------------------------------------------------------- */\n/* Members sit 4px apart and share one height. Every control already agrees on\n   that height — `--atom-control-h`, see the Control surface block in\n   tokens.css — so the only member that needs handling is Button, which keeps\n   its own 47px: `stretch` cannot pull a fixed height, so the height is\n   released and the row decides. */\n.atom-field__control {\n  display: flex;\n  align-items: stretch;\n  gap: var(--atom-x1);\n  min-width: 0;\n}\n\n/* Every member starts at `width: 100%`, so no explicit `flex` is needed — they\n   all ask for the full row and shrink together. What must not shrink says so\n   below; what is left over goes to the typing surfaces, and two inputs in a\n   range land on the same width without being told. */\n.atom-field__control > * {\n  min-width: 0;\n}\n\n/* `:not(:only-child)` matters: a Button alone in the slot has nothing taller to\n   stretch against, so releasing its height would collapse it to its own text.\n   The height only gives way once there is a row to match. Every other control\n   is already `--atom-control-h` and needs nothing here. */\n.atom-field__control > .atom-button:not(:only-child) {\n  height: auto;\n  flex-shrink: 0;\n}\n\n\n/* Focus follows the control ring too. base.css draws its ring *outside* the\n   element at a 2px offset, which lands on the neighbour across a 4px gap, so a\n   Button in a combination borrows the inset ring every other control uses. */\n.atom-field__control > .atom-button:not(:only-child):focus-visible {\n  outline: none;\n  box-shadow: var(--atom-control-border-focus);\n}\n\n/* An icon button in the slot is square. Its own `--layout_icon-only` widths\n   (32 / 40 / 47 / 54px) follow Button's heights, and in the slot the row sets\n   the height instead — so the width is re-pointed at the control heights the\n   row can take, picked by the size modifier the author already put on the\n   button to match the control beside it.\n\n   `aspect-ratio` cannot do this: flex resolves an item's width before the row\n   stretches its height, so with `width: auto` there is no definite height for\n   the ratio to work from and the button collapses to its icon (0 wide, once\n   the icon is a percentage of it).\n\n   The icon is a share of the box rather than a fixed size, so it tracks all\n   three heights — 40 → 16px, 48 → 19.2px, 56 → 22.4px — which is the 38–42%\n   band Button's own sizes already sit in. */\n.atom-field__control > .atom-button--layout_icon-only:not(:only-child) {\n  width: var(--atom-control-h);\n  padding-inline: 0;\n}\n\n.atom-field__control\n  > .atom-button--layout_icon-only.atom-button--size_xsmall:not(:only-child),\n.atom-field__control\n  > .atom-button--layout_icon-only.atom-button--size_small:not(:only-child) {\n  width: var(--atom-control-h-small);\n}\n\n.atom-field__control\n  > .atom-button--layout_icon-only.atom-button--size_large:not(:only-child) {\n  width: var(--atom-control-h-large);\n}\n\n.atom-field__control\n  > .atom-button--layout_icon-only:not(:only-child)\n  .atom-button__icon {\n  width: 40%;\n  /* Not `height: 40%` — the row's height is auto, so a percentage height falls\n     back to the svg's 150px intrinsic height and the row grows to fit it. */\n  height: auto;\n  aspect-ratio: 1;\n}\n\n/* Checkbox, Radio and Switch are a line of text with a marker, not a box: they\n   have no control height, so a slot holding only those is as tall as its text\n   and sits above the 48px inputs in the fields beside it. Give that slot the\n   control height and centre the group in it — the marker then lines up with\n   the text inside those inputs. `:has` because the slot, not the group, is the\n   box being sized. */\n.atom-field__control:has(> .atom-checkbox-group:only-child),\n.atom-field__control:has(> .atom-radio-group:only-child),\n.atom-field__control:has(> .atom-checkbox:only-child),\n.atom-field__control:has(> .atom-radio:only-child),\n.atom-field__control:has(> .atom-switch:only-child) {\n  align-items: center;\n  min-height: var(--atom-control-h);\n}\n\n.atom-field--size_small\n  .atom-field__control:has(> .atom-checkbox-group:only-child),\n.atom-field--size_small\n  .atom-field__control:has(> .atom-radio-group:only-child),\n.atom-field--size_small .atom-field__control:has(> .atom-checkbox:only-child),\n.atom-field--size_small .atom-field__control:has(> .atom-radio:only-child),\n.atom-field--size_small .atom-field__control:has(> .atom-switch:only-child) {\n  min-height: var(--atom-control-h-small);\n}\n\n/* The ‘~’ or ‘부터’ between a range's two inputs. It is a label, not a control,\n   so it keeps its own size and centres against the row instead of stretching. */\n.atom-field__separator {\n  flex-shrink: 0;\n  align-self: center;\n  padding-inline: 2px;\n  font-size: 15px;\n  color: var(--atom-fg-neutral-muted);\n  white-space: nowrap;\n}\n\n.atom-field__footer {\n  display: flex;\n  align-items: flex-start;\n  justify-content: space-between;\n  gap: var(--atom-x4);\n  font-size: 12px;\n  line-height: 1.5;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-field__description,\n.atom-field__error {\n  min-width: 0;\n  word-break: keep-all;\n}\n\n.atom-field__error {\n  display: flex;\n  align-items: flex-start;\n  gap: 4px;\n  color: var(--atom-fg-critical);\n}\n\n.atom-field__error-icon {\n  width: 13px;\n  height: 13px;\n  flex-shrink: 0;\n  margin-top: 2px;\n}\n\n.atom-field__counter {\n  flex-shrink: 0;\n  margin-left: auto;\n  font-variant-numeric: tabular-nums;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-field--size_small .atom-field__label {\n  font-size: 12px;\n}\n\n.atom-field--size_small .atom-field__footer {\n  font-size: 11px;\n}\n\n/* --- horizontal layout --------------------------------------------------- */\n.atom-field--layout_horizontal {\n  flex-direction: row;\n  align-items: flex-start;\n  gap: var(--atom-x5);\n}\n\n.atom-field--layout_horizontal .atom-field__header {\n  flex-shrink: 0;\n  width: 120px;\n  padding-top: 12px;\n}\n\n.atom-field--layout_horizontal .atom-field__body {\n  display: flex;\n  flex-direction: column;\n  gap: 6px;\n  flex: 1;\n  min-width: 0;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-field[data-invalid] .atom-field__description {\n  color: var(--atom-fg-critical);\n}\n\n.atom-field[data-disabled] {\n  opacity: 0.45;\n}\n\n/* Legacy alias — the old card preview marked invalid with a modifier class. */\n.atom-field--invalid .atom-field__description {\n  color: var(--atom-fg-critical);\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/field.css"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/text-input.css",
      "content": "/* ============================================================================\n   Element — Text Input & Textarea\n   ----------------------------------------------------------------------------\n   One box, two shapes. `--textarea` grows to a multi-line height; everything\n   else — sizes, prefix/suffix slots, states — is shared.\n\n   atom-text-input\n     --size_{small|medium|large}             medium is the default\n     --textarea\n     --variant_{outline|weak}                outline is the default\n     --focused  --invalid  --readonly\n     [disabled] on the inner control\n     __prefix  __suffix  __control  __placeholder  __caret  __clear\n   ========================================================================== */\n\n.atom-text-input {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  width: 100%;\n  min-width: 0;\n  min-height: var(--atom-control-h);\n  padding: var(--atom-x3) var(--atom-x4);\n  border-radius: var(--atom-radius-r2);\n  background: var(--atom-bg-layer);\n  box-shadow: var(--atom-control-border);\n  font-size: 15px;\n  line-height: 1.45;\n  color: var(--atom-fg-neutral);\n  transition: box-shadow var(--atom-duration-fast) ease;\n}\n\n/* The real <input>/<textarea>. Borderless — the wrapper draws the box. */\n.atom-text-input__control {\n  flex: 1;\n  min-width: 0;\n  border: 0;\n  padding: 0;\n  background: none;\n  outline: none;\n  font: inherit;\n  color: inherit;\n  resize: none;\n}\n\n.atom-text-input__control::placeholder {\n  color: var(--atom-fg-neutral-subtle);\n}\n\n.atom-text-input__prefix,\n.atom-text-input__suffix {\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-muted);\n  font-size: 14px;\n}\n\n.atom-text-input__prefix svg,\n.atom-text-input__suffix svg {\n  width: 18px;\n  height: 18px;\n}\n\n.atom-text-input__clear {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 20px;\n  height: 20px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-full);\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-text-input__clear svg {\n  width: 12px;\n  height: 12px;\n}\n\n/* Static stand-ins so a demo can show placeholder / caret without focus. */\n.atom-text-input__placeholder {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n.atom-text-input__caret {\n  display: inline-block;\n  width: 1.5px;\n  height: 19px;\n  flex-shrink: 0;\n  margin-right: 1px;\n  background: var(--atom-fg-neutral);\n  animation: atom-text-input-blink 1.1s steps(1) infinite;\n}\n\n@keyframes atom-text-input-blink {\n  50% {\n    opacity: 0;\n  }\n}\n\n/* --- textarea ------------------------------------------------------------ */\n.atom-text-input--textarea {\n  min-height: 92px;\n  align-items: flex-start;\n}\n\n.atom-text-input--textarea .atom-text-input__control {\n  min-height: 66px;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-text-input--size_small {\n  min-height: var(--atom-control-h-small);\n  padding: var(--atom-x2) var(--atom-x3);\n  font-size: 14px;\n}\n\n.atom-text-input--size_small .atom-text-input__caret {\n  height: 17px;\n}\n\n.atom-text-input--size_large {\n  min-height: var(--atom-control-h-large);\n  padding-inline: var(--atom-x5);\n  border-radius: var(--atom-radius-r3);\n  font-size: 16px;\n}\n\n/* --- variants ------------------------------------------------------------ */\n.atom-text-input--variant_weak {\n  background: var(--atom-bg-neutral-weak);\n  box-shadow: none;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-text-input:hover:not(.atom-text-input--focused):not(\n    .atom-text-input--invalid\n  ):not(.atom-text-input--readonly) {\n  box-shadow: var(--atom-control-border-hover);\n}\n\n.atom-text-input--focused,\n.atom-text-input:focus-within {\n  box-shadow: var(--atom-control-border-focus);\n}\n\n.atom-text-input--invalid,\n.atom-text-input--invalid:focus-within {\n  box-shadow: var(--atom-control-border-invalid);\n}\n\n.atom-text-input--readonly {\n  background: var(--atom-bg-neutral-weak);\n  box-shadow: none;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-text-input:has(.atom-text-input__control[disabled]),\n.atom-text-input--disabled {\n  opacity: 0.45;\n  background: var(--atom-bg-neutral-weak);\n  box-shadow: none;\n  cursor: not-allowed;\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/text-input.css"
    }
  ],
  "categories": [
    "display"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "component",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "Field",
      "Field.Header",
      "Field.Label",
      "Field.Optional",
      "Field.Control",
      "Field.Separator",
      "Field.Footer",
      "Field.Description",
      "Field.Error",
      "Field.Counter"
    ],
    "docs": "poc/design-system/components/field.md"
  }
}
