{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-button",
  "type": "registry:ui",
  "title": "Input Button",
  "description": "입력처럼 보이지만 실제로는 버튼입니다. 값을 고르는 일이 다른 화면에서 일어날 때 씁니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/icons",
    "@atom/lib-bem",
    "@atom/lib-cn",
    "@atom/lib-field-context",
    "@atom/lib-slot"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/input-button.tsx",
      "content": "\"use client\";\n\nimport { type ButtonHTMLAttributes, type MouseEventHandler, type ReactNode, type Ref, useId } from \"react\";\nimport { mod } from \"../lib/bem\";\nimport { cn } from \"../lib/cn\";\nimport { useFieldControl } from \"../lib/field-context\";\nimport { Slot } from \"../lib/slot\";\nimport { ChevronRightIcon } from \"./icons\";\n\n/* -------------------------------------------------------------------------- */\n/*  Input Button (components/input-button.md,                                 */\n/*                styles/atom/elements/input-button.css)                      */\n/*    <InputButton                                                            */\n/*      prefixIcon={<CalendarIcon />}                                         */\n/*      value=\"2026년 8월 19일\"                                               */\n/*      placeholder=\"날짜를 선택해주세요\"                                       */\n/*      onClick={openPicker}                                                  */\n/*    />                                                                      */\n/*  원본은 hidden · specSource 없음 · Specification 미완성입니다.               */\n/* -------------------------------------------------------------------------- */\n\nexport type InputButtonSize = \"small\" | \"medium\" | \"large\";\nexport type InputButtonVariant = \"outline\" | \"weak\" | \"ghost\";\n\nexport type InputButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"value\" | \"onClick\"> & {\n  ref?: Ref<HTMLButtonElement>;\n  /** 선택된 값입니다. 없으면 placeholder 가 보입니다. */\n  value?: ReactNode;\n  /** 값이 없을 때 보여줄 안내 문구입니다. */\n  placeholder?: string;\n  /** 높이와 폰트 크기를 결정합니다. 기본 \"medium\" — 지정하지 않으면 Field 의 size 를 따릅니다. */\n  size?: InputButtonSize;\n  /** 배경과 테두리 처리를 결정합니다. */\n  variant?: InputButtonVariant;\n  /** 값 앞에 놓이는 아이콘입니다. */\n  prefixIcon?: ReactNode;\n  /** 테두리를 오류 색으로 바꿉니다. */\n  invalid?: boolean;\n  /** 누를 수 없게 만듭니다. */\n  disabled?: boolean;\n  /** 값을 고르는 화면을 여는 핸들러입니다. */\n  onClick: MouseEventHandler<HTMLButtonElement>;\n};\n\nconst B = \"atom-input-button\";\n\nfunction isEmpty(value: ReactNode) {\n  return value === undefined || value === null || value === false || value === \"\";\n}\n\nexport function InputButton({\n  ref,\n  value,\n  placeholder,\n  size: sizeProp,\n  variant = \"outline\",\n  prefixIcon,\n  invalid = false,\n  disabled = false,\n  onClick,\n  type = \"button\",\n  id,\n  className,\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...rest\n}: InputButtonProps) {\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 empty = isEmpty(value);\n  const valueId = useId();\n\n  return (\n    <button\n      {...rest}\n      ref={ref}\n      type={type}\n      id={id ?? field?.controlId}\n      disabled={isDisabled}\n      className={cn(\n        B,\n        mod(B, \"size\", size, \"medium\"),\n        mod(B, \"variant\", variant, \"outline\"),\n        empty && `${B}--empty`,\n        className,\n      )}\n      data-invalid={isInvalid ? \"\" : undefined}\n      // Field 라벨 + 현재 값을 함께 읽힘 (\"거래 희망 장소, 지도에서 선택해주세요\")\n      aria-labelledby={ariaLabelledBy ?? (field ? `${field.labelId} ${valueId}` : undefined)}\n      aria-describedby={cn(field?.describedBy, ariaDescribedBy) || undefined}\n      onClick={onClick}\n    >\n      {prefixIcon !== undefined && (\n        <Slot aria-hidden=\"true\" className={`${B}__prefix`}>\n          {prefixIcon}\n        </Slot>\n      )}\n      <span id={valueId} className={`${B}__value`}>\n        {empty ? placeholder : value}\n      </span>\n      <ChevronRightIcon className={`${B}__caret`} />\n    </button>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/input-button.tsx"
    },
    {
      "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/input-button.css",
      "content": "/* ============================================================================\n   Element — Input Button\n   ----------------------------------------------------------------------------\n   A button that looks like an input. Used when picking a value happens\n   somewhere else — a bottom sheet, a date picker, another screen — so there is\n   nothing to type into.\n\n   atom-input-button\n     --size_{small|medium|large}             medium is the default\n     --variant_{outline|weak|ghost}          outline is the default\n     --empty                                 no value chosen yet\n     [data-invalid]  [disabled]\n     __prefix  __value  __caret\n   ========================================================================== */\n\n.atom-input-button {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: var(--atom-x3);\n  width: 100%;\n  min-width: 0;\n  height: var(--atom-control-h);\n  padding-inline: var(--atom-x4);\n  border-radius: var(--atom-radius-r3);\n  background: var(--atom-bg-layer);\n  box-shadow: var(--atom-control-border);\n  font-size: 15px;\n  color: var(--atom-fg-neutral);\n  text-align: left;\n  transition: box-shadow var(--atom-duration-fast) ease,\n    background-color var(--atom-duration-fast) ease;\n}\n\n.atom-input-button__prefix {\n  width: 20px;\n  height: 20px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-muted);\n}\n\n.atom-input-button__value {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.atom-input-button__caret {\n  width: 18px;\n  height: 18px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n/* --- variants ------------------------------------------------------------ */\n.atom-input-button--variant_weak {\n  background: var(--atom-bg-neutral-weak);\n  box-shadow: none;\n}\n\n.atom-input-button--variant_ghost {\n  background: transparent;\n  box-shadow: none;\n  padding-inline: 0;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-input-button--size_small {\n  height: var(--atom-control-h-small);\n  border-radius: var(--atom-radius-r2);\n  font-size: 14px;\n}\n\n.atom-input-button--size_small .atom-input-button__caret {\n  width: 16px;\n  height: 16px;\n}\n\n.atom-input-button--size_large {\n  height: var(--atom-control-h-large);\n  font-size: 16px;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-input-button--empty .atom-input-button__value {\n  color: var(--atom-fg-neutral-subtle);\n}\n\n.atom-input-button:hover:not([disabled]) {\n  background: var(--atom-bg-neutral-weak);\n}\n\n.atom-input-button--variant_weak:hover:not([disabled]) {\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-input-button:focus-visible {\n  outline: none;\n  box-shadow: var(--atom-control-border-focus);\n}\n\n.atom-input-button[data-invalid] {\n  box-shadow: var(--atom-control-border-invalid);\n}\n\n.atom-input-button[disabled] {\n  opacity: 0.4;\n  cursor: not-allowed;\n  background: var(--atom-bg-layer);\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/input-button.css"
    }
  ],
  "categories": [
    "action-input"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "element",
    "status": "stable",
    "specSource": null,
    "hidden": true,
    "parts": [
      "InputButton"
    ],
    "docs": "poc/design-system/components/input-button.md"
  }
}
