Replies: 19 comments 22 replies
-
This looks good except it doesn't place the magnifying glass inside the input. This is because the classes for styling the input are directly on the input and not on the div surrounding both elements. Here's a screenshot. Considering this, I moved styles to an outer div and allowed the same kind of customization as the input. Some styles need to be on the root div like focus-within (within because we're focusing the input not the div) and alignment then some styles need to be on the input itself like padding to get the click target correct.
This snippet appears as such with icon inside the input. Note though it's not inside the input, it's inside the div which is styled to look like an input. |
Beta Was this translation helpful? Give feedback.
-
This is how i was able to get the icon in the inputarea
|
Beta Was this translation helpful? Give feedback.
-
I did this way,
and then can use like this:
|
Beta Was this translation helpful? Give feedback.
-
My solution: import React from 'react'
import { SearchIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
const separatePaddingClasses = (className: string | undefined) => {
const inputClassesPrefixes = [
'p-',
'px-',
'py-',
'pl-',
'pr-',
'pt-',
'pb-',
'placeholder:',
]
const bothElementsClassesPrefixes = ['bg-']
const allClasses = className ? className.split(' ') : []
const bothElementsClasses = allClasses.filter((currentClass) =>
bothElementsClassesPrefixes.some((prefix) =>
currentClass.startsWith(prefix),
),
)
const inputClasses = allClasses.filter((currentClass) =>
inputClassesPrefixes.some((prefix) => currentClass.startsWith(prefix)),
)
const otherClasses = allClasses.filter(
(currentClass) =>
!inputClassesPrefixes.some((prefix) => currentClass.startsWith(prefix)),
)
return {
inputClasses: [...inputClasses, ...bothElementsClasses].join(' '),
otherClasses: [...otherClasses, ...bothElementsClasses].join(' '),
}
}
const SearchInput = React.forwardRef<HTMLInputElement, InputProps>(
({ className, placeholder, ...props }, ref) => {
const { inputClasses, otherClasses } = separatePaddingClasses(className)
return (
<div
className={cn(
'relative flex rounded-lg border border-input bg-transparent pr-3 text-sm text-input shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-input-placeholder focus-within:border-primary focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
otherClasses,
)}
>
<SearchIcon className="absolute right-4 top-1/2 z-10 -translate-y-1/2 transform text-primary" />
<input
type="text"
placeholder={placeholder}
className={cn(
'w-full rounded-bl-lg rounded-tl-lg border-0 py-2.5 pl-3 outline-none',
inputClasses,
)}
ref={ref}
{...props}
/>
</div>
)
},
)
SearchInput.displayName = 'Search'
export { SearchInput } The |
Beta Was this translation helpful? Give feedback.
-
You should be able to just compose it as such: import { inputVariants } from "@/components";
import React from "react";
import { SearchIcon } from "lucide-react";
export const Search: React.FC = () => {
return (
<label
className={inputVariants({
className: "[&:has(:focus-visible)]:ring-ring flex items-center p-0 [&:has(:focus-visible)]:ring-2",
})}
>
<span className="sr-only">Search</span>
<SearchIcon className="size-4" />
<input
type="search"
placeholder="Search"
className="size-full ml-2 border-none bg-transparent focus:outline-none"
/>
</label>
);
}; |
Beta Was this translation helpful? Give feedback.
-
I've just developed a solution that enhances input components. This solution allows for the seamless integration of icons with inputs, particularly adding support for icons in password inputs, complete with Caps Lock indication and eye icon toggling. "use client";
import * as React from "react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { ArrowBigUpDash, EyeIcon, EyeOffIcon } from "lucide-react";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
Icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ Icon, className, type, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
const [capsLockActive, setCapsLockActive] = React.useState(false);
const handleKeyPress: React.KeyboardEventHandler<HTMLInputElement> = (
event
) => {
const capsLockOn = event.getModifierState("CapsLock");
setCapsLockActive(capsLockOn);
};
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const inputClasses = cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
Icon && "pl-10",
type === "password" && (!capsLockActive ? "pr-8" : "pr-16"),
className
);
return (
<div className={cn("relative", className)}>
{Icon && (
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<Icon />
</div>
)}
<input
type={type === "password" && showPassword ? "text" : type}
className={inputClasses}
onKeyDown={handleKeyPress}
ref={ref}
{...props}
/>
{type === "password" && (
<div className="absolute right-0 flex items-center pr-3 -translate-y-1/2 top-1/2 gap-x-1">
{showPassword ? (
<EyeOffIcon
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
) : (
<EyeIcon
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
)}
{capsLockActive && type === "password" && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<ArrowBigUpDash size={20} />
</TooltipTrigger>
<TooltipContent>
<p>Caps Lock is on!</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
)}
</div>
);
}
);
Input.displayName = "Input";
export { Input }; |
Beta Was this translation helpful? Give feedback.
-
@F-47 Thanks for the implementation, it's great! I changed the Icon prop to be Also, in the outermost div, I added |
Beta Was this translation helpful? Give feedback.
-
This is what I did with my implimentation, this way you can customize the icon however you like. For example I can add a
|
Beta Was this translation helpful? Give feedback.
-
This is an input field for entering a password. The icon next to it toggles the visibility of the password. import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
icon?: React.ReactNode;
onShow?: () => void;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, icon, onShow, ...props }, ref) => {
return (
<div
className={cn(
"flex h-10 items-center rounded-md border border-input bg-white pl-3 text-sm ring-offset-background focus-within:ring-1 focus-within:ring-ring focus-within:ring-offset-2",
className
)}>
<input
type={type}
className="w-full p-2 placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
ref={ref}
{...props}
/>
<span onClick={onShow} className="pr-4 cursor-pointer">
{icon}
</span>
</div>
);
}
);
Input.displayName = "Input";
export { Input }; |
Beta Was this translation helpful? Give feedback.
-
import * as React from "react"; export interface InputProps const Input = React.forwardRef<HTMLInputElement, InputProps>( {icon && iconPosition === "left" && ( {icon} )} <input type={type} className={cn( "flex h-10 w-full rounded-md border border-input bg-white py-2 pl-14 pr-12 text-sm ring-offset-primary file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className, )} ref={ref} {...props} /> {icon && iconPosition === "right" && ( {icon} )} ); }, ); Input.displayName = "Input"; export { Input }; |
Beta Was this translation helpful? Give feedback.
-
import * as React from "react" import { cn } from "@/lib/utils" export interface InputProps const Input = React.forwardRef<HTMLInputElement, InputProps>( {props.icon && ( <props.icon size={20} className="absolute opacity-65 left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground" /> )} <input type={type !== "abn" ? type : "number"} onChange={props.onChange} className={cn( "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50", props.icon && "pl-10", className )} ref={ref} min={0} {...props} /> ) } ) Input.displayName = "Input" export { Input } |
Beta Was this translation helpful? Give feedback.
-
Tried to make a solution using composition pattern, but couldn't get the focus state for the father div right (using Expo/Reusables). import { cva, type VariantProps } from 'class-variance-authority'
import type { LucideProps } from 'lucide-react-native'
import * as React from 'react'
import { TextInput, View } from 'react-native'
import { cn } from '~/lib/utils'
const inputContainerVariants = cva(
'flex flex-row items-center group web:group-focus-visible:border-4 ring-2 ring-input bg-background px-[10px] py-[7px] group web:focus-within:ring-2 web:group-focus-visible:ring-2 web:ring-offset-background',
{
variants: {
radius: {
default: 'rounded-[100px]',
sm: 'rounded-[10px]',
},
},
defaultVariants: {
radius: 'default',
},
}
)
type InputContainerProps = React.ComponentPropsWithoutRef<typeof View> &
VariantProps<typeof inputContainerVariants> & {
children: React.ReactNode
}
const InputContainer = React.forwardRef<React.ElementRef<typeof View>, InputContainerProps>(
({ children, className, radius, ...props }, ref) => {
return (
<View
ref={ref}
className={cn(inputContainerVariants({ className, radius }))}
{...props}
>
{children}
</View>
)
}
)
InputContainer.displayName = 'InputContainer'
type InputIconProps = { icon: React.ComponentType<LucideProps> } & LucideProps
const InputIcon = ({ icon: Icon, size = 20, ...props }: InputIconProps) => {
return (
<View className='p-2.5 -mb-1'>
<Icon
className='text-primary'
size={size}
{...props}
/>
</View>
)
}
InputIcon.displayName = 'InputIcon'
const Input = React.forwardRef<
React.ElementRef<typeof TextInput>,
React.ComponentPropsWithoutRef<typeof TextInput>
>(({ className, placeholderClassName, ...props }, ref) => {
return (
<TextInput
ref={ref}
className={cn(
'web:flex h-10 native:h-12 web:w-full rounded-md px-2.5 mr-2.5 group web:py-2.5 text-base lg:text-sm group native:text-lg native:leading-[1.25] text-foreground placeholder:text-muted-foreground web:ring-offset-background file:border-0 file:bg-transparent file:font-medium web:focus-visible:outline-none web:focus-visible:ring-0',
props.editable === false && 'opacity-50 web:cursor-not-allowed',
className
)}
placeholderClassName={cn('text-muted-foreground', placeholderClassName)}
{...props}
/>
)
})
Input.displayName = 'Input'
export { Input, InputContainer, InputIcon } |
Beta Was this translation helpful? Give feedback.
-
I have a simple extension of the base icon component, with support for Start icons, End icons, Password hide/reveal functionality and CapsLock active indication. ` export interface InputProps const Input = React.forwardRef<HTMLInputElement, InputProps>(
} export { Input }; |
Beta Was this translation helpful? Give feedback.
-
🙌 My Solution for Input password with toggle Eye Icon using shadcn/ui and radix-ui/react-icons 👁.import * as React from "react" import { cn } from "@/lib/utils"; export interface InputProps {/* NORMAL INPUT */}
} {/* PASSWORD INPUT WITH TOGGLE EYE ICON*/} ({ className, type, ...props }, ref) => {
} Input.displayName = "Input"; export { Input, InputPassword } |
Beta Was this translation helpful? Give feedback.
-
My solution with both startIcon and endIcon const Input = React.forwardRef(({ className, type, startIcon, endIcon, onStartIconClick, onEndIconClick, ...props }, ref) => {
const StartIcon = startIcon;
const EndIcon = endIcon;
return (
<div className="w-full relative flex items-center">
{startIcon && <button
className="absolute left-3 text-center transition-all disabled:pointer-events-none disabled:opacity-50"
type="button"
onClick={onStartIconClick}
>
<StartIcon className={"w-4 h-4 text-foreground-faded"} />
</button>
}
<input
type={type}
className={cn("w-full py-2 rounded-sm bg-background placeholder:text-foreground-faded border border-border-faded transition duration-300 ease focus:outline-none focus:border-border-primary",
startIcon && 'pl-9',
endIcon && 'pr-9',
className
)}
ref={ref}
{...props }
/>
{endIcon && <button
className="absolute right-3 text-center transition-all disabled:pointer-events-none disabled:opacity-50"
type="button"
onClick={onEndIconClick}
>
<EndIcon className="w-4 h-4 text-foreground" />
</button>
}
</div>
);
})
Input.displayName = "Input"
export { Input } |
Beta Was this translation helpful? Give feedback.
-
Hi all, I've created a PR to make it easier to use icons |
Beta Was this translation helpful? Give feedback.
-
A bit late but heres how i did it import { cn } from "@/lib/utils"; export interface InputProps {icon && ( {icon} )}{" "} {/* Render icon if provided */} <input type={type} className={cn( flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${icon && "pl-8"} ,className, )} ref={ref} {...props} /> ); }, ); Input.displayName = "Input"; export { Input }; |
Beta Was this translation helpful? Give feedback.
-
The most flexible solution I've found: import * as React from 'react'
import { cn } from '~/lib/utils'
type IconProps = React.SVGProps<SVGSVGElement> & { children?: never }
type IconPropsWithBehavior<T extends IconProps> = T & { behavior: 'append' | 'prepend' }
type IconComponent<T extends IconProps = IconProps> = React.ComponentType<T>
export type InputProps<T extends IconComponent = IconComponent> = React.InputHTMLAttributes<HTMLInputElement> & {
icon?: T
iconProps: T extends IconComponent<infer P> ? IconPropsWithBehavior<P> : never
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, icon, iconProps: { behavior: iconBehavior, className: iconClassName, ...iconProps }, ...props }, ref) => {
const Icon = icon
return (
<div
className={cn(
'flex items-center justify-center m-0 p-0 rounded-md border border-input bg-transparent px-3 py-0 text-sm shadow-sm transition-colors focus-within:outline-none focus-within:ring-1 focus-within:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className
)}
>
{Icon && type !== 'file' && iconBehavior === 'prepend' && (
<Icon className={cn('w-4 h-4 mr-3 text-muted-foreground', iconClassName)} {...iconProps} />
)}
<input
type={type}
className={cn(
'flex items-center justify-center h-9 w-full bg-transparent placeholder:text-muted-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
type !== 'file' ? 'py-1' : 'py-1.5',
className
)}
ref={ref}
{...props}
/>
{Icon && type !== 'file' && iconBehavior === 'append' && (
<Icon className={cn('w-4 h-4 ml-3 text-muted-foreground', iconClassName)} {...iconProps} />
)}
</div>
)
}
)
Input.displayName = 'Input'
export { Input } Usage: import { Input } from '~/components/ui/input'
import { MagnifyingGlassIcon as SearchIcon } from '@radix-ui/react-icons'
<Input type='search' icon={SearchIcon} iconProps={{ behavior: 'prepend' }} placeholder='Search...' /> Preview: |
Beta Was this translation helpful? Give feedback.
-
Below is my proposal. import * as React from "react"
import { cn } from "@/utils/utils"
import { ArrowBigUpDash, Eye, EyeClosed, Search } from "lucide-react";
import { useKeyPress } from "ahooks";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
type InputProps = React.PropsWithChildren<React.ComponentProps<"input">> & {
icon?: React.ReactNode
}
const Input = React.forwardRef < HTMLInputElement, InputProps> (
({ children, icon, className, type, ...props }, ref) => {
const classes = cn(
'flex h-10 w-full gap-x-2 rounded-md border border-input bg-background px-3 py-2 text-base has-[:focus-visible]:outline-none has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2 has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 md:text-sm',
className
);
return (
<div className={classes}>
{icon}
<input
type={type}
className="flex-1 h-full bg-background outline-none file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed"
ref={ref}
{...props}
/>
{children}
</div>
)
}
)
Input.displayName = "Input"
const InputPassword = React.forwardRef < HTMLInputElement, Omit<React.ComponentProps<'input' >, 'type' >> (
(props, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const [capsLockActive, setCapsLockActive] = React.useState(false);
useKeyPress(
() => true,
e => {
if (e.getModifierState) {
setCapsLockActive(e.getModifierState('CapsLock'))
}
}
);
return (
<Input ref={ref} {...props} type={showPassword ? "text" : 'password'}>
<div className="flex items-center gap-x-1 self-center">
{showPassword ? (
<EyeClosed
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
) : (
<Eye
className="cursor-pointer"
onClick={togglePasswordVisibility}
size={20}
/>
)}
{capsLockActive && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<ArrowBigUpDash size={20} />
</TooltipTrigger>
<TooltipContent>
<p>Caps Lock is on!</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
</Input>
);
}
);
InputPassword.displayName = 'InputPassword';
const InputSearch = React.forwardRef < HTMLInputElement, React.ComponentProps< 'input' >> (
(props, ref) => {
return <Input ref={ref} {...props} icon={<Search size={20} className="self-center" />} />;
}
);
export { Input, InputPassword, InputSearch } |
Beta Was this translation helpful? Give feedback.
-
Do there have official way to do the input with icon?
I got
my implementation but I would like to know is there have the proper way to do it?
Beta Was this translation helpful? Give feedback.
All reactions