import { type ComponentType, type HTMLAttributes, type ReactNode, type Ref, createContext, useContext } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCircleQuestion, faCircleExclamation } from "@fortawesome/pro-duotone-svg-icons"; import type { InputProps as AriaInputProps, TextFieldProps as AriaTextFieldProps } from "react-aria-components"; import { Group as AriaGroup, Input as AriaInput, TextField as AriaTextField } from "react-aria-components"; import { HintText } from "@/components/base/input/hint-text"; import { Label } from "@/components/base/input/label"; import { Tooltip, TooltipTrigger } from "@/components/base/tooltip/tooltip"; import { cx, sortCx } from "@/utils/cx"; export interface InputBaseProps extends TextFieldProps { /** Tooltip message on hover. */ tooltip?: string; /** * Input size. * @default "sm" */ size?: "sm" | "md"; /** Placeholder text. */ placeholder?: string; /** Class name for the icon. */ iconClassName?: string; /** Class name for the input. */ inputClassName?: string; /** Class name for the input wrapper. */ wrapperClassName?: string; /** Class name for the tooltip. */ tooltipClassName?: string; /** Keyboard shortcut to display. */ shortcut?: string | boolean; ref?: Ref; groupRef?: Ref; /** Icon component to display on the left side of the input. */ icon?: ComponentType>; } export const InputBase = ({ ref, tooltip, shortcut, groupRef, size = "sm", isInvalid, isDisabled, icon: Icon, placeholder, wrapperClassName, tooltipClassName, inputClassName, iconClassName, // Omit this prop to avoid invalid HTML attribute warning isRequired: _isRequired, ...inputProps }: Omit) => { // Check if the input has a leading icon or tooltip const hasTrailingIcon = tooltip || isInvalid; const hasLeadingIcon = Icon; // If the input is inside a `TextFieldContext`, use its context to simplify applying styles const context = useContext(TextFieldContext); const inputSize = context?.size || size; const sizes = sortCx({ sm: { root: cx("px-3 py-2", hasTrailingIcon && "pr-9", hasLeadingIcon && "pl-10"), iconLeading: "left-3", iconTrailing: "right-3", shortcut: "pr-2.5", }, md: { root: cx("px-3.5 py-2.5", hasTrailingIcon && "pr-9.5", hasLeadingIcon && "pl-10.5"), iconLeading: "left-3.5", iconTrailing: "right-3.5", shortcut: "pr-3", }, }); return ( cx( "relative flex w-full flex-row place-content-center place-items-center rounded-lg bg-primary shadow-xs ring-1 ring-primary transition-shadow duration-100 ease-linear ring-inset", isFocusWithin && !isDisabled && "ring-2 ring-brand", // Disabled state styles isDisabled && "cursor-not-allowed bg-disabled_subtle ring-disabled", "group-disabled:cursor-not-allowed group-disabled:bg-disabled_subtle group-disabled:ring-disabled", // Invalid state styles isInvalid && "ring-error_subtle", "group-invalid:ring-error_subtle", // Invalid state with focus-within styles isInvalid && isFocusWithin && "ring-2 ring-error", isFocusWithin && "group-invalid:ring-2 group-invalid:ring-error", context?.wrapperClassName, wrapperClassName, ) } > {/* Leading icon and Payment icon */} {Icon && ( )} {/* Input field */} {/* Tooltip and help icon */} {tooltip && !isInvalid && ( )} {/* Invalid icon */} {isInvalid && ( )} {/* Shortcut */} {shortcut && (
)}
); }; InputBase.displayName = "InputBase"; interface BaseProps { /** Label text for the input */ label?: string; /** Helper text displayed below the input */ hint?: ReactNode; } interface TextFieldProps extends BaseProps, AriaTextFieldProps, Pick { ref?: Ref; } const TextFieldContext = createContext({}); export const TextField = ({ className, ...props }: TextFieldProps) => { return ( cx("group flex h-max w-full flex-col items-start justify-start gap-1.5", typeof className === "function" ? className(state) : className) } /> ); }; TextField.displayName = "TextField"; interface InputProps extends InputBaseProps, BaseProps { /** Whether to hide required indicator from label */ hideRequiredIndicator?: boolean; } export const Input = ({ size = "sm", placeholder, icon: Icon, label, hint, shortcut, hideRequiredIndicator, className, ref, groupRef, tooltip, iconClassName, inputClassName, wrapperClassName, tooltipClassName, ...props }: InputProps) => { return ( {({ isRequired, isInvalid }) => ( <> {label && } {hint && {hint}} )} ); }; Input.displayName = "Input";