{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mc-input",
  "title": "MicroClub Input",
  "description": "An input component with field integration and addon support for MicroClub UI",
  "dependencies": [
    "@base-ui/react"
  ],
  "files": [
    {
      "path": "registry/ui/mc-input.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { Input as InputPrimitive } from '@base-ui/react/input';\n\nimport { cn } from '@/lib/utils';\n\nexport interface McInputProps extends React.ComponentProps<'input'> {\n  /** Label rendered above the input. */\n  label?: React.ReactNode;\n  /** Help text below the input. Replaced by error when present. */\n  description?: React.ReactNode;\n  /** Truthy = error state. ReactNode = renders the message. `true` = red border only. */\n  error?: React.ReactNode;\n  /** Inline-start addon (icons, text, dropdowns). */\n  addonStart?: React.ReactNode;\n  /** Inline-end addon (buttons, badges, spinners). */\n  addonEnd?: React.ReactNode;\n  /** Block-start addon (toolbar, controls above the input row). */\n  addonTop?: React.ReactNode;\n  /** Block-end addon (character count, status bar below the input row). */\n  addonBottom?: React.ReactNode;\n}\n\nfunction McInput({\n  className,\n  label,\n  description,\n  error,\n  addonStart,\n  addonEnd,\n  addonTop,\n  addonBottom,\n  type,\n  id,\n  disabled,\n  required,\n  ...props\n}: McInputProps) {\n  const generatedId = React.useId();\n  const inputId = id ?? generatedId;\n\n  const hasError = !!error;\n  const errorMessage = typeof error === 'boolean' ? null : error;\n  const showError = hasError && !!errorMessage;\n  const showDescription = !hasError && !!description;\n  const messageId = showError\n    ? `${inputId}-error`\n    : showDescription\n      ? `${inputId}-description`\n      : undefined;\n\n  const handleGroupClick = (e: React.MouseEvent<HTMLDivElement>) => {\n    const target = e.target as HTMLElement;\n    if (!target.closest('button, a, input, select, textarea, [tabindex]')) {\n      e.currentTarget.querySelector<HTMLInputElement>('[data-slot=\"input\"]')?.focus();\n    }\n  };\n\n  return (\n    <div data-slot=\"input-root\" className={cn('flex flex-col gap-1.5', className)}>\n      {label && (\n        <label\n          data-slot=\"input-label\"\n          htmlFor={inputId}\n          className=\"text-sm font-medium text-muted-foreground\"\n        >\n          {label}\n        </label>\n      )}\n\n      <div\n        data-slot=\"input-group\"\n        data-error={hasError || undefined}\n        data-disabled={disabled || undefined}\n        data-has-top={!!addonTop || undefined}\n        data-has-bottom={!!addonBottom || undefined}\n        role=\"group\"\n        onClick={handleGroupClick}\n        className={cn(\n          // Structure\n          'group/input flex min-w-0 cursor-text flex-col rounded-lg border border-border bg-input shadow-xs transition-all',\n          // Focus ring\n          'focus-within:ring-4 focus-within:ring-ring',\n          // Error\n          'data-error:border-destructive',\n          'data-error:focus-within:border-destructive data-error:focus-within:ring-destructive/20',\n          'dark:data-error:border-destructive/60 dark:data-error:focus-within:ring-destructive/30',\n          // Disabled\n          'data-disabled:pointer-events-none data-disabled:opacity-50'\n        )}\n      >\n        {addonTop && (\n          <div\n            data-slot=\"input-addon\"\n            data-align=\"top\"\n            className={cn(\n              'flex w-full items-center gap-2 border-b border-border px-3.5 py-1.5 text-sm text-muted-foreground select-none',\n              \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\"\n            )}\n          >\n            {addonTop}\n          </div>\n        )}\n\n        {/*\n          has-data-attached:items-stretch — When an attached McInputButton is\n          anywhere inside this row, switch from center to stretch so the addon\n          container fills the full row height and the button can match it.\n        */}\n        <div data-slot=\"input-row\" className=\"flex items-center has-data-attached:items-stretch\">\n          {addonStart && (\n            <div\n              data-slot=\"input-addon\"\n              data-align=\"start\"\n              className={cn(\n                // group/addon — lets attached buttons detect start/end via group-data-[align]/addon\n                // :has(>svg:first-child):pl-3.5 — widen padding only when the edge element is an icon\n                // has-data-attached:pl-0 — flush for attached buttons (attribute selector beats type selector)\n                // has-data-attached:items-stretch — let attached button fill full height\n                'group/addon flex shrink-0 items-center gap-2 pl-2 text-sm text-muted-foreground select-none [&:has(>svg:first-child)]:pl-3.5 has-data-attached:items-stretch has-data-attached:pl-0',\n                \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\"\n              )}\n            >\n              {addonStart}\n            </div>\n          )}\n\n          <InputPrimitive\n            data-slot=\"input\"\n            type={type}\n            id={inputId}\n            disabled={disabled}\n            required={required}\n            aria-invalid={hasError || undefined}\n            aria-describedby={messageId}\n            className={cn(\n              'h-10 flex-1 min-w-0 bg-transparent px-3.5 py-2.5 text-sm outline-none placeholder:text-muted-foreground file:inline-flex file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground disabled:cursor-not-allowed',\n              // Hide number input spinners across all browsers\n              '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',\n              addonStart && 'pl-2',\n              addonEnd && 'pr-2'\n            )}\n            {...props}\n          />\n\n          {addonEnd && (\n            <div\n              data-slot=\"input-addon\"\n              data-align=\"end\"\n              className={cn(\n                // Same as addonStart, mirrored (pl → pr, first-child → last-child)\n                'group/addon flex shrink-0 items-center gap-2 pr-2 text-sm text-muted-foreground select-none [&:has(>svg:last-child)]:pr-3.5 has-data-attached:items-stretch has-data-attached:pr-0',\n                \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\"\n              )}\n            >\n              {addonEnd}\n            </div>\n          )}\n        </div>\n\n        {addonBottom && (\n          <div\n            data-slot=\"input-addon\"\n            data-align=\"bottom\"\n            className={cn(\n              'flex w-full items-center gap-2 border-t border-border px-3.5 py-1.5 text-sm text-muted-foreground select-none',\n              \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\"\n            )}\n          >\n            {addonBottom}\n          </div>\n        )}\n      </div>\n\n      {showError && (\n        <p\n          data-slot=\"input-error\"\n          id={`${inputId}-error`}\n          role=\"alert\"\n          className=\"text-sm text-destructive\"\n        >\n          {errorMessage}\n        </p>\n      )}\n\n      {showDescription && (\n        <p\n          data-slot=\"input-description\"\n          id={`${inputId}-description`}\n          className=\"text-sm text-muted-foreground\"\n        >\n          {description}\n        </p>\n      )}\n    </div>\n  );\n}\n\nexport interface McInputButtonProps extends React.ComponentProps<'button'> {\n  /** Visual style of the button. `attached` extends to the input edge and shares its border. */\n  variant?: 'ghost' | 'outline' | 'filled' | 'attached';\n}\n\nfunction McInputButton({\n  className,\n  variant = 'ghost',\n  type = 'button',\n  ...props\n}: McInputButtonProps) {\n  const isAttached = variant === 'attached';\n\n  return (\n    <button\n      type={type}\n      data-slot=\"input-button\"\n      data-attached={isAttached || undefined}\n      className={cn(\n        // Base\n        'inline-flex cursor-pointer items-center justify-center gap-1.5 text-sm font-medium whitespace-nowrap transition-colors outline-none select-none disabled:pointer-events-none disabled:opacity-50',\n        \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n\n        // Ghost\n        variant === 'ghost' &&\n          'h-7 rounded-md px-2 bg-transparent text-muted-foreground hover:bg-accent hover:text-foreground',\n\n        // Outline\n        variant === 'outline' &&\n          'h-7 rounded-md px-3 border border-border bg-transparent text-foreground hover:bg-accent',\n\n        // Filled\n        variant === 'filled' &&\n          'h-7 rounded-md px-3 bg-primary text-primary-foreground hover:bg-primary/90',\n\n        // Attached — flush with input border, auto-detects position via group/addon.\n        // Radius is rounded-lg minus 1px to sit inside the group's border.\n        isAttached && [\n          'h-full px-4 bg-primary text-primary-foreground hover:bg-primary/90 border-border',\n          'group-data-[align=end]/addon:rounded-l-none group-data-[align=end]/addon:rounded-r-[calc(0.5rem-1px)] group-data-[align=end]/addon:border-l',\n          'group-data-[align=start]/addon:rounded-r-none group-data-[align=start]/addon:rounded-l-[calc(0.5rem-1px)] group-data-[align=start]/addon:border-r',\n          // Flatten corners adjacent to block addons (addonTop / addonBottom)\n          '[[data-has-top]_[data-align]_&]:rounded-tl-none [[data-has-top]_[data-align]_&]:rounded-tr-none',\n          '[[data-has-bottom]_[data-align]_&]:rounded-bl-none [[data-has-bottom]_[data-align]_&]:rounded-br-none',\n        ],\n\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { McInput, McInputButton };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}