{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "text-input",
  "type": "registry:ui",
  "title": "Text Input & Textarea",
  "description": "키보드로 값을 직접 입력하는 컴포넌트입니다. 한 줄과 여러 줄이 같은 박스를 공유합니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/icons",
    "@atom/lib-bem",
    "@atom/lib-cn",
    "@atom/lib-field-context"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/text-input.tsx",
      "content": "\"use client\";\n\nimport {\n  type ChangeEvent,\n  type FocusEvent,\n  type InputHTMLAttributes,\n  type ReactNode,\n  type Ref,\n  useRef,\n  useState,\n} from \"react\";\nimport { mod } from \"../lib/bem\";\nimport { cn } from \"../lib/cn\";\nimport { useFieldControl } from \"../lib/field-context\";\nimport { CloseIcon } from \"./icons\";\n\n/* -------------------------------------------------------------------------- */\n/*  Text Input & Textarea (components/text-input.md,                          */\n/*                         styles/atom/elements/text-input.css)               */\n/*    <TextInput prefix=\"₩\" suffix=\"원\" inputMode=\"numeric\" aria-label=\"금액\" />*/\n/*    <TextInput shape=\"textarea\" rows={3} maxLength={200} />                 */\n/*    <TextInput prefix={<SearchGlassIcon />} clearable placeholder=\"검색\" />  */\n/*  Textarea 는 독립 컴포넌트가 아니라 shape=\"textarea\" (--textarea modifier) */\n/* -------------------------------------------------------------------------- */\n\nexport type TextInputShape = \"textInput\" | \"textarea\";\nexport type TextInputSize = \"small\" | \"medium\" | \"large\";\nexport type TextInputVariant = \"outline\" | \"weak\";\nexport type TextInputElement = HTMLInputElement | HTMLTextAreaElement;\n\n/**\n * `className` · `style` 은 Container(atom-text-input) 에, 나머지 HTML 속성(id · placeholder · type ·\n * inputMode · value · onChange …)과 `ref` 는 Control(<input>/<textarea>) 에 붙습니다.\n */\nexport type TextInputProps = Omit<InputHTMLAttributes<TextInputElement>, \"size\" | \"prefix\"> & {\n  ref?: Ref<TextInputElement>;\n  /** 한 줄 · 여러 줄 중 어느 것으로 렌더할지 결정합니다. */\n  shape?: TextInputShape;\n  /** 높이와 폰트 크기를 결정합니다. 기본 \"medium\" — 지정하지 않으면 Field 의 size 를 따릅니다. */\n  size?: TextInputSize;\n  /** 테두리와 배경 처리를 결정합니다. */\n  variant?: TextInputVariant;\n  /** 입력 앞에 고정되는 아이콘이나 단위입니다. */\n  prefix?: ReactNode;\n  /** 입력 뒤에 고정되는 아이콘이나 단위입니다. */\n  suffix?: ReactNode;\n  /** 값이 있을 때 Clear 버튼을 노출합니다. */\n  clearable?: boolean;\n  /** 테두리를 오류 색으로 바꿉니다. */\n  invalid?: boolean;\n  /** 값은 읽을 수 있고 수정만 막습니다. */\n  readOnly?: boolean;\n  /** 읽기와 제출 모두 막습니다. */\n  disabled?: boolean;\n  /** `textarea` 일 때의 기본 줄 수입니다. */\n  rows?: number;\n  /** 입력 가능한 최대 글자 수입니다. */\n  maxLength?: number;\n};\n\nconst B = \"atom-text-input\";\n\n/** React 가 onChange 를 받도록 네이티브 setter 로 값을 바꾸고 input 이벤트를 보냅니다. */\nfunction setNativeValue(el: TextInputElement, next: string) {\n  const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;\n  Object.getOwnPropertyDescriptor(proto, \"value\")?.set?.call(el, next);\n  el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n}\n\nexport function TextInput({\n  ref,\n  shape = \"textInput\",\n  size: sizeProp,\n  variant = \"outline\",\n  prefix,\n  suffix,\n  clearable = false,\n  invalid = false,\n  readOnly = false,\n  disabled = false,\n  rows = 3,\n  className,\n  style,\n  id,\n  value,\n  defaultValue,\n  onChange,\n  onFocus,\n  onBlur,\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-invalid\": ariaInvalid,\n  required,\n  ...rest\n}: TextInputProps) {\n  const field = useFieldControl();\n  const size = sizeProp ?? field?.size ?? \"medium\";\n  const isInvalid = invalid || !!field?.invalid;\n  const isDisabled = disabled || !!field?.disabled;\n  const isTextarea = shape === \"textarea\";\n\n  const controlRef = useRef<TextInputElement | null>(null);\n  const [focused, setFocused] = useState(false);\n  // uncontrolled 일 때 Clear 노출 여부를 위해 값의 유무만 추적\n  const [innerFilled, setInnerFilled] = useState(() => String(defaultValue ?? \"\") !== \"\");\n  const filled = value !== undefined ? String(value) !== \"\" : innerFilled;\n  const showClear = clearable && filled && focused && !readOnly && !isDisabled;\n\n  const controlProps = {\n    ...rest,\n    ref: (node: TextInputElement | null) => {\n      controlRef.current = node;\n      if (typeof ref === \"function\") return ref(node);\n      if (ref) ref.current = node;\n    },\n    id: id ?? field?.controlId,\n    className: `${B}__control`,\n    value,\n    defaultValue,\n    readOnly,\n    disabled: isDisabled,\n    required: required ?? (field?.required || undefined),\n    \"aria-invalid\": ariaInvalid ?? (isInvalid || undefined),\n    \"aria-describedby\": cn(field?.describedBy, ariaDescribedBy) || undefined,\n    onChange: (e: ChangeEvent<TextInputElement>) => {\n      if (value === undefined) setInnerFilled(e.target.value !== \"\");\n      onChange?.(e);\n    },\n  };\n\n  return (\n    <div\n      className={cn(\n        B,\n        mod(B, \"size\", size, \"medium\"),\n        isTextarea && `${B}--textarea`,\n        mod(B, \"variant\", variant, \"outline\"),\n        focused && `${B}--focused`,\n        isInvalid && `${B}--invalid`,\n        readOnly && `${B}--readonly`,\n        isDisabled && `${B}--disabled`,\n        className,\n      )}\n      style={style}\n      onFocus={(e: FocusEvent<HTMLDivElement>) => {\n        setFocused(true);\n        if (e.target === controlRef.current) onFocus?.(e as unknown as FocusEvent<TextInputElement>);\n      }}\n      onBlur={(e: FocusEvent<HTMLDivElement>) => {\n        // Clear 버튼으로 이동하는 것처럼 Container 안에서 옮겨갈 때는 focused 유지\n        if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setFocused(false);\n        if (e.target === controlRef.current) onBlur?.(e as unknown as FocusEvent<TextInputElement>);\n      }}\n    >\n      {prefix !== undefined && <span className={`${B}__prefix`}>{prefix}</span>}\n      {isTextarea ? (\n        <textarea {...(controlProps as InputHTMLAttributes<HTMLTextAreaElement>)} rows={rows} />\n      ) : (\n        <input {...(controlProps as InputHTMLAttributes<HTMLInputElement>)} />\n      )}\n      {showClear && (\n        <button\n          type=\"button\"\n          className={`${B}__clear`}\n          aria-label=\"지우기\"\n          // 포커스를 Control 에 남겨 Clear 가 사라지지 않게\n          onMouseDown={(e) => e.preventDefault()}\n          onClick={() => {\n            const el = controlRef.current;\n            if (!el) return;\n            setNativeValue(el, \"\");\n            el.focus();\n          }}\n        >\n          <CloseIcon />\n        </button>\n      )}\n      {suffix !== undefined && <span className={`${B}__suffix`}>{suffix}</span>}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/text-input.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/checkbox.css",
      "content": "/* ============================================================================\n   Element — Checkbox\n   ----------------------------------------------------------------------------\n   atom-checkbox\n     --size_{small|medium|large}             medium is the default\n     --align_top                             label wraps to more than one line\n     [data-state=\"unchecked|checked|indeterminate\"]\n     [data-invalid]  [data-disabled]\n     __control  __check  __indeterminate  __label  __description\n\n   atom-checkbox-group  (+ --layout_horizontal)\n\n   Reads as a real control: <label> + a visually-hidden <input type=\"checkbox\">\n   also works, since :checked is wired up alongside the data-state attribute.\n   ========================================================================== */\n\n.atom-checkbox {\n  display: inline-flex;\n  align-items: center;\n  gap: 10px;\n  font-size: 15px;\n  line-height: 1.45;\n  color: var(--atom-fg-neutral);\n  cursor: pointer;\n}\n\n.atom-checkbox--align_top {\n  align-items: flex-start;\n}\n\n.atom-checkbox__input {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  opacity: 0;\n  pointer-events: none;\n}\n\n.atom-checkbox__control {\n  position: relative;\n  width: 22px;\n  height: 22px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-r1);\n  background: var(--atom-bg-layer);\n  box-shadow: inset 0 0 0 1.5px var(--atom-border-default);\n  transition: background-color var(--atom-duration-fast) ease,\n    box-shadow var(--atom-duration-fast) ease;\n}\n\n.atom-checkbox--align_top .atom-checkbox__control {\n  margin-top: 1px;\n}\n\n.atom-checkbox__check,\n.atom-checkbox__indeterminate {\n  position: absolute;\n  inset: 0;\n  margin: auto;\n  color: var(--atom-fg-on-solid);\n  opacity: 0;\n  transition: opacity var(--atom-duration-fast) ease;\n}\n\n.atom-checkbox__check {\n  width: 14px;\n  height: 14px;\n}\n\n.atom-checkbox__indeterminate {\n  width: 10px;\n  height: 2px;\n  border-radius: var(--atom-radius-full);\n  background: currentColor;\n}\n\n.atom-checkbox__text {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n  min-width: 0;\n}\n\n.atom-checkbox__description {\n  font-size: 13px;\n  color: var(--atom-fg-neutral-muted);\n  word-break: keep-all;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-checkbox--size_small {\n  gap: 8px;\n  font-size: 14px;\n}\n\n.atom-checkbox--size_small .atom-checkbox__control {\n  width: 18px;\n  height: 18px;\n}\n\n.atom-checkbox--size_small .atom-checkbox__check {\n  width: 12px;\n  height: 12px;\n}\n\n.atom-checkbox--size_large {\n  gap: 12px;\n  font-size: 16px;\n}\n\n.atom-checkbox--size_large .atom-checkbox__control {\n  width: 26px;\n  height: 26px;\n  border-radius: 5px;\n}\n\n.atom-checkbox--size_large .atom-checkbox__check {\n  width: 17px;\n  height: 17px;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-checkbox[data-state=\"checked\"] .atom-checkbox__control,\n.atom-checkbox[data-state=\"indeterminate\"] .atom-checkbox__control,\n.atom-checkbox__input:checked + .atom-checkbox__control {\n  background: var(--atom-bg-neutral-solid);\n  box-shadow: inset 0 0 0 1.5px var(--atom-bg-neutral-solid);\n}\n\n.atom-checkbox[data-state=\"checked\"] .atom-checkbox__check,\n.atom-checkbox__input:checked + .atom-checkbox__control .atom-checkbox__check {\n  opacity: 1;\n}\n\n.atom-checkbox[data-state=\"indeterminate\"] .atom-checkbox__indeterminate {\n  opacity: 1;\n}\n\n.atom-checkbox:hover:not([data-disabled]) .atom-checkbox__control {\n  box-shadow: inset 0 0 0 1.5px var(--atom-fg-neutral-subtle);\n}\n\n.atom-checkbox[data-state=\"checked\"]:hover:not([data-disabled])\n  .atom-checkbox__control,\n.atom-checkbox[data-state=\"indeterminate\"]:hover:not([data-disabled])\n  .atom-checkbox__control {\n  box-shadow: inset 0 0 0 1.5px var(--atom-bg-neutral-solid);\n  filter: brightness(0.9);\n}\n\n.atom-checkbox__input:focus-visible + .atom-checkbox__control {\n  outline: 2px solid var(--atom-primary-600);\n  outline-offset: 2px;\n}\n\n.atom-checkbox[data-invalid] .atom-checkbox__control {\n  box-shadow: inset 0 0 0 1.5px var(--atom-fg-critical);\n}\n\n.atom-checkbox[data-invalid] .atom-checkbox__description {\n  color: var(--atom-fg-critical);\n}\n\n.atom-checkbox[data-disabled] {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n/* --- group --------------------------------------------------------------- */\n.atom-checkbox-group {\n  display: flex;\n  flex-direction: column;\n  gap: var(--atom-x4);\n}\n\n.atom-checkbox-group--layout_horizontal {\n  flex-direction: row;\n  flex-wrap: wrap;\n  gap: var(--atom-x5);\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/checkbox.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/select.css",
      "content": "/* ============================================================================\n   Element — Select\n   ----------------------------------------------------------------------------\n   A trigger that opens a listbox. Options carry an optional leading icon, a\n   label, a description and a selected check.\n\n   atom-select\n     --size_{small|medium|large}             medium is the default\n     --variant_{weak|outline}                weak is the default\n     [data-state=\"closed|open\"]  [data-invalid]  [data-disabled]\n     __trigger  __value  __caret\n     __listbox  __group-label  __option  __option-icon  __option-text\n     __option-label  __option-description  __option-check\n     __empty\n   ========================================================================== */\n\n.atom-select {\n  position: relative;\n  width: 100%;\n  min-width: 0;\n}\n\n/* --- trigger ------------------------------------------------------------- */\n.atom-select__trigger {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--atom-x3);\n  width: 100%;\n  height: var(--atom-control-h);\n  padding-inline: var(--atom-x4);\n  border-radius: var(--atom-radius-r2);\n  background: var(--atom-bg-neutral-weak);\n  font-size: 15px;\n  color: var(--atom-fg-neutral);\n  text-align: left;\n  transition: box-shadow var(--atom-duration-fast) ease,\n    filter var(--atom-duration-fast) ease;\n}\n\n.atom-select__value {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.atom-select--empty .atom-select__value,\n.atom-select__trigger--empty .atom-select__value {\n  color: var(--atom-fg-neutral-subtle);\n}\n\n.atom-select__caret {\n  width: 24px;\n  height: 24px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-muted);\n  transition: transform var(--atom-duration-base) var(--atom-ease-out);\n}\n\n/* --- listbox ------------------------------------------------------------- */\n.atom-select__listbox {\n  margin-top: 6px;\n  padding: 6px;\n  border-radius: var(--atom-radius-r3);\n  background: var(--atom-bg-layer);\n  box-shadow: var(--atom-shadow-s2), inset 0 0 0 1px var(--atom-border-subtle);\n}\n\n.atom-select__group-label {\n  padding: 6px 10px 4px;\n  font-size: 11px;\n  font-weight: 600;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n.atom-select__option {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  padding: 9px 10px;\n  border-radius: var(--atom-radius-r2);\n  font-size: 14px;\n  cursor: pointer;\n  transition: background-color var(--atom-duration-fast) ease;\n}\n\n.atom-select__option-icon {\n  width: 20px;\n  height: 20px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-select__option-text {\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n  flex: 1;\n}\n\n.atom-select__option-label {\n  font-weight: 500;\n  color: var(--atom-fg-neutral);\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.atom-select__option-description {\n  font-size: 12px;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-select__option-check {\n  width: 18px;\n  height: 18px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral);\n  opacity: 0;\n}\n\n.atom-select__empty {\n  padding: var(--atom-x5) 10px;\n  text-align: center;\n  font-size: 13px;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-select--size_small .atom-select__trigger {\n  height: var(--atom-control-h-small);\n  font-size: 14px;\n}\n\n.atom-select--size_small .atom-select__caret {\n  width: 24px;\n  height: 24px;\n}\n\n.atom-select--size_large .atom-select__trigger {\n  height: var(--atom-control-h-large);\n  border-radius: var(--atom-radius-r3);\n  font-size: 16px;\n}\n\n/* --- variants ------------------------------------------------------------ */\n.atom-select--variant_outline .atom-select__trigger {\n  background: var(--atom-bg-layer);\n  box-shadow: var(--atom-control-border);\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-select__trigger:hover {\n  filter: brightness(0.96);\n}\n\n/* One ring for every control — see the Control surface block in tokens.css.\n   The trigger is the focusable element here (Input hands focus to an inner\n   <input>), so base.css's outset ring is replaced rather than layered: an\n   outset ring with a 2px offset crosses the 4px gap to its neighbour when a\n   field puts this next to another control. Placed above the invalid rule so\n   invalid keeps outranking focus. */\n.atom-select__trigger:focus-visible {\n  outline: none;\n  box-shadow: var(--atom-control-border-focus);\n}\n\n.atom-select[data-state=\"open\"] .atom-select__caret {\n  transform: rotate(180deg);\n}\n\n.atom-select[data-state=\"closed\"] .atom-select__listbox {\n  display: none;\n}\n\n.atom-select__option[aria-selected=\"true\"] {\n  background: var(--atom-bg-neutral-weak);\n}\n\n.atom-select__option[aria-selected=\"true\"] .atom-select__option-check {\n  opacity: 1;\n}\n\n.atom-select__option:hover:not([aria-disabled=\"true\"]) {\n  background: var(--atom-bg-neutral-weak);\n}\n\n.atom-select__option[aria-disabled=\"true\"] {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n.atom-select[data-invalid] .atom-select__trigger {\n  box-shadow: var(--atom-control-border-invalid);\n}\n\n.atom-select[data-disabled] {\n  opacity: 0.4;\n  pointer-events: none;\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/select.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": [
    "action-input"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "element",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "TextInput"
    ],
    "docs": "poc/design-system/components/text-input.md"
  }
}
