import type { CSSProperties, FocusEvent, KeyboardEvent, ReactNode } from "react"; import type { IconNames } from "../Icon"; /** * `className`/`style` shared by every `InputNumber` building block. * Both compose with the block's internal styling rather than replacing it. */ export interface InputNumberStyleProps { readonly className?: string; readonly style?: CSSProperties; } /** * Affix descriptor for the top-level `prefix`/`suffix` props. At least one of * `label` or `icon` should be provided. Icons render outside the input area (in * the field's outer padding zone); labels render inside the input area next to * the typed value. `onClick`/`ariaLabel` are not allowed here; use * `InputNumberSuffix` for a clickable action. */ export interface InputNumberAffix { readonly label?: string; readonly icon?: IconNames; readonly ariaLabel?: never; readonly onClick?: never; } /** * Suffix variant that turns the suffix icon into a clickable action button. * The `?: never` fields make this a discriminated union with the plain affix. */ export interface InputNumberSuffix { readonly label?: string; readonly icon: IconNames; readonly ariaLabel: string; onClick(): void; } export type InputNumberSuffixProp = InputNumberAffix | InputNumberSuffix; export type InputNumberAutoComplete = boolean | "on" | "off" | "one-time-code" | "address-line1" | "address-line2"; export type InputNumberSize = "small" | "default" | "large"; /** * Field configuration shared by `` (the * composition entry point) and the top-level `` sugar. * The Wrapper owns the field state and exposes it to the parts via context. */ export interface InputNumberWrapperProps extends InputNumberStyleProps { readonly "aria-label"?: string; readonly "aria-labelledby"?: string; readonly align?: "center" | "right"; readonly autoComplete?: InputNumberAutoComplete; /** Composed parts (`.Group`, `.Footer`, and the parts within them). */ readonly children?: ReactNode; readonly disabled?: boolean; /** * Number formatting for the displayed value, forwarded to Base UI * `NumberField`'s native `format`. When omitted, typed decimals are preserved. */ readonly format?: Intl.NumberFormatOptions; readonly id?: string; /** Shrink-wrap the field to its content (auto width). */ readonly inline?: boolean; /** * Virtual keyboard hint for mobile. When omitted, Base UI picks the * keyboard: `"numeric"` on most platforms, `"decimal"` or `"text"` on iOS * (its numeric keypad has no decimal or minus key). */ readonly inputMode?: "numeric" | "decimal"; /** Style the error border without showing an error message. */ readonly invalid?: boolean; /** * Shows a non-blocking loading indicator in the stepper's slot. The field * stays editable (use `readOnly`/`disabled` to lock it); the stepper hides so * the two never overlap. */ readonly loading?: boolean; readonly max?: number; readonly min?: number; readonly name?: string; /** * Also fires once per Enter press: committing on Enter is implemented as a * `blur()`/`focus()` round-trip, so `onBlur` and `onFocus` both fire even * though focus never visibly leaves the field. */ readonly onBlur?: (event?: FocusEvent) => void; /** * Fires when the user commits a value (blur, Enter, stepper, arrow step). * Emits `null` when committed empty. For per-keystroke updates use * `onValueChange`. */ readonly onValueCommitted?: (newValue: number | null) => void; /** * Fires on every parsed value change (typing, paste, stepper, arrow step). * Emits `null` when the field is empty. */ readonly onValueChange?: (newValue: number | null) => void; /** Fires when Enter is pressed without modifier keys (Shift/Ctrl/Meta). */ readonly onEnter?: (event: KeyboardEvent) => void; /** * Also fires once per Enter press; see `onBlur` for the commit mechanics. */ readonly onFocus?: (event?: FocusEvent) => void; readonly onKeyDown?: (event: KeyboardEvent) => void; readonly onKeyUp?: (event: KeyboardEvent) => void; readonly readOnly?: boolean; /** Default `true`. When `false`, the floating label is hidden. */ readonly showMiniLabel?: boolean; readonly size?: InputNumberSize; /** * Amount the stepper buttons and ArrowUp/ArrowDown keys change the value by. * Default `"any"`, which steps by `1` while accepting any decimal value. Pass a * number to both set the step increment and constrain valid values to its * multiples (a non-multiple then fails native validation). */ readonly step?: number | "any"; /** * Controlled value. `number` sets the value; `null` (or `undefined`) is an * empty field. */ readonly value?: number | null; } /** * Props for the top-level `` sugar: the field * configuration plus the convenience content props (`label`, `description`, * `error`, `prefix`, `suffix`). It composes the parts internally — to compose * the field yourself, use `` and the parts. */ export interface InputNumberProps extends Omit { /** Floating field label. */ readonly label?: string; readonly description?: ReactNode; /** Renders a styled error message below the field. */ readonly error?: string; readonly prefix?: InputNumberAffix; readonly suffix?: InputNumberSuffixProp; } export interface InputNumberRef { blur(): void; focus(): void; } export interface InputNumberGroupProps extends InputNumberStyleProps { readonly children?: ReactNode; } export interface InputNumberFooterProps extends InputNumberStyleProps { readonly children?: ReactNode; } export interface InputNumberInputProps extends InputNumberStyleProps { /** Content rendered inside the input area (e.g. `.Label`, `.Stepper`). */ readonly children?: ReactNode; } export interface InputNumberLabelProps extends InputNumberStyleProps { readonly children?: ReactNode; } export interface InputNumberDescriptionProps extends InputNumberStyleProps { readonly children?: ReactNode; } export interface InputNumberErrorProps extends InputNumberStyleProps { /** Error message. Rendered with an alert icon via the `HelperText` primitive. */ readonly children?: string; } export interface InputNumberLoadingProps extends InputNumberStyleProps { /** Indicator content. Falls back to the default `ActivityIndicator`. */ readonly children?: ReactNode; } export interface InputNumberStepperProps extends InputNumberStyleProps { /** Stepper buttons. Falls back to the default increment/decrement pair. */ readonly children?: ReactNode; /** Accessible label for the increment button. Defaults to `Increase value`. */ readonly incrementLabel?: string; /** Accessible label for the decrement button. Defaults to `Decrease value`. */ readonly decrementLabel?: string; } export interface InputNumberStepButtonProps extends InputNumberStyleProps { /** Icon content. Falls back to the default Atlantis stepper icon. */ readonly children?: ReactNode; /** Accessible label for the button. */ readonly ariaLabel?: string; } export interface InputNumberAffixCompound extends InputNumberStyleProps { readonly variation: "prefix" | "suffix"; readonly label?: string; readonly icon?: IconNames; readonly ariaLabel?: never; readonly onClick?: never; /** Arbitrary affix content, beyond the built-in `label`/`icon`. */ readonly children?: ReactNode; } export interface InputNumberAffixCompoundClickable extends InputNumberStyleProps { readonly variation: "prefix" | "suffix"; readonly label?: string; readonly icon: IconNames; readonly ariaLabel: string; onClick(): void; /** Arbitrary affix content, beyond the built-in `label`/`icon`. */ readonly children?: ReactNode; } /** * `.Affix` props. Discriminated by whether `onClick` is set: * - omit `onClick` -> behaves like a plain affix * - set `onClick` -> requires `icon` and `ariaLabel` (clickable icon-button) */ export type InputNumberAffixCompoundProps = InputNumberAffixCompound | InputNumberAffixCompoundClickable;