// AlignUI Input v0.0.0 import * as React from 'react'; import { tv, type VariantProps } from '@/utils/tv'; import { recursiveCloneChildren } from '@/utils/recursive-clone-children'; import type { PolymorphicComponentProps } from '@/utils/polymorphic'; import { Slot } from '@radix-ui/react-slot'; const INPUT_ROOT_NAME = 'InputRoot'; const INPUT_WRAPPER_NAME = 'InputWrapper'; const INPUT_EL_NAME = 'InputEl'; const INPUT_ICON_NAME = 'InputIcon'; const INPUT_AFFIX_NAME = 'InputAffixButton'; const INPUT_INLINE_AFFIX_NAME = 'InputInlineAffixButton'; export const inputVariants = tv({ slots: { root: [ // base 'group bg-bg-white-0 text-text-strong-950 shadow-regular-xs relative flex w-full overflow-hidden', 'transition duration-200 ease-out', 'divide-stroke-soft-200 divide-x', // before 'before:ring-stroke-soft-200 before:absolute before:inset-0 before:ring-1 before:ring-inset', 'before:pointer-events-none before:rounded-[inherit]', 'before:transition before:duration-200 before:ease-out', // hover 'hover:shadow-none', // focus 'focus-input-within:shadow-button-important-focus focus-input-within:before:ring-stroke-strong-950', // disabled 'has-[input:disabled]:shadow-none has-[input:disabled]:before:ring-transparent', ], wrapper: [ // base 'group/input-wrapper bg-bg-white-0 flex w-full cursor-text items-center', 'transition duration-200 ease-out', // hover 'hover:[&:not(&:has(input:focus))]:bg-bg-weak-50', // disabled 'has-[input:disabled]:bg-bg-weak-50 has-[input:disabled]:pointer-events-none', ], input: [ // base 'text-paragraph-sm text-text-strong-950 w-full bg-transparent bg-none outline-none', 'transition duration-200 ease-out', // placeholder 'placeholder:text-text-soft-400 placeholder:transition placeholder:duration-200 placeholder:ease-out placeholder:select-none', // hover placeholder 'group-hover/input-wrapper:placeholder:text-text-sub-600', // focus 'focus:outline-none', // focus placeholder 'group-has-[input:focus]:placeholder:text-text-sub-600', // disabled 'disabled:text-text-disabled-300 disabled:placeholder:text-text-disabled-300', ], icon: [ // base 'flex size-5 shrink-0 items-center justify-center select-none', 'transition duration-200 ease-out', // placeholder state 'group-has-[:placeholder-shown]:text-text-soft-400', // filled state 'text-text-sub-600', // hover 'group-has-[:placeholder-shown]:group-hover/input-wrapper:text-text-sub-600', // focus 'group-has-[:placeholder-shown]:group-has-[input:focus]/input-wrapper:text-text-sub-600', // disabled 'group-has-[input:disabled]/input-wrapper:text-text-disabled-300', ], affix: [ // base 'bg-bg-white-0 text-paragraph-sm text-text-sub-600 shrink-0', 'flex items-center justify-center truncate', 'transition duration-200 ease-out', // placeholder state 'group-has-[:placeholder-shown]:text-text-soft-400', // focus state 'group-has-[:placeholder-shown]:group-has-[input:focus]:text-text-sub-600', ], inlineAffix: [ // base 'text-paragraph-sm text-text-sub-600', // placeholder state 'group-has-[:placeholder-shown]:text-text-soft-400', // focus state 'group-has-[:placeholder-shown]:group-has-[input:focus]:text-text-sub-600', ], }, variants: { size: { medium: { root: 'rounded-10', wrapper: 'gap-2 px-3', input: 'h-10', }, small: { root: 'rounded-lg', wrapper: 'gap-2 px-2.5', input: 'h-9', }, xsmall: { root: 'rounded-lg', wrapper: 'gap-1.5 px-2', input: 'h-8', }, }, hasError: { true: { root: [ // base 'before:ring-error-base', // base 'hover:before:ring-error-base hover:[&:not(&:has(input:focus)):has(>:only-child)]:before:ring-error-base', // focus 'focus-input-within:shadow-button-error-focus focus-input-within:before:ring-error-base', ], }, false: { root: [ // hover 'hover:[&:not(:has(input:focus)):has(>:only-child)]:before:ring-transparent', ], }, }, }, compoundVariants: [ //#region affix { size: 'medium', class: { affix: 'px-3', }, }, { size: ['small', 'xsmall'], class: { affix: 'px-2.5', }, }, //#endregion ], defaultVariants: { size: 'medium', }, }); type InputSharedProps = VariantProps; function InputRoot({ className, children, size, hasError, asChild, ...rest }: React.HTMLAttributes & InputSharedProps & { asChild?: boolean; }) { const uniqueId = React.useId(); const Component = asChild ? Slot : 'div'; const { root } = inputVariants({ size, hasError, }); const sharedProps: InputSharedProps = { size, hasError, }; const extendedChildren = recursiveCloneChildren( children as React.ReactElement[], sharedProps, [ INPUT_WRAPPER_NAME, INPUT_EL_NAME, INPUT_ICON_NAME, INPUT_AFFIX_NAME, INPUT_INLINE_AFFIX_NAME, ], uniqueId, asChild, ); return ( {extendedChildren} ); } InputRoot.displayName = INPUT_ROOT_NAME; function InputWrapper({ className, children, size, hasError, asChild, ...rest }: React.HTMLAttributes & InputSharedProps & { asChild?: boolean; }) { const Component = asChild ? Slot : 'label'; const { wrapper } = inputVariants({ size, hasError, }); return ( {children} ); } InputWrapper.displayName = INPUT_WRAPPER_NAME; const Input = React.forwardRef< HTMLInputElement, React.InputHTMLAttributes & InputSharedProps & { asChild?: boolean; } >( ( { className, type = 'text', size, hasError, asChild, ...rest }, forwardedRef, ) => { const Component = asChild ? Slot : 'input'; const { input } = inputVariants({ size, hasError, }); return ( ); }, ); Input.displayName = INPUT_EL_NAME; function InputIcon({ size, hasError, as, className, ...rest }: PolymorphicComponentProps) { const Component = as || 'div'; const { icon } = inputVariants({ size, hasError }); return ; } InputIcon.displayName = INPUT_ICON_NAME; function InputAffix({ className, children, size, hasError, ...rest }: React.HTMLAttributes & InputSharedProps) { const { affix } = inputVariants({ size, hasError, }); return (
{children}
); } InputAffix.displayName = INPUT_AFFIX_NAME; function InputInlineAffix({ className, children, size, hasError, ...rest }: React.HTMLAttributes & InputSharedProps) { const { inlineAffix } = inputVariants({ size, hasError, }); return ( {children} ); } InputInlineAffix.displayName = INPUT_INLINE_AFFIX_NAME; export { InputRoot as Root, InputWrapper as Wrapper, Input, InputIcon as Icon, InputAffix as Affix, InputInlineAffix as InlineAffix, };