-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(input-component): support icons left/right
- Loading branch information
Showing
11 changed files
with
302 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { MapIcon, MapPin } from "lucide-react" | ||
|
||
import { Input } from "@/registry/default/ui/input" | ||
|
||
export default function InputWithIcons() { | ||
return ( | ||
<div> | ||
<Input> | ||
<Input.Icon side="left"> | ||
<MapIcon /> | ||
</Input.Icon> | ||
<Input.Icon side="right"> | ||
<MapPin /> | ||
</Input.Icon> | ||
</Input> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as React from "react" | ||
|
||
type ReactElementWithDisplayName = React.ReactElement & { | ||
type: { displayName: string } | ||
} | ||
|
||
const isElement = ( | ||
element: unknown | undefined | ||
): element is ReactElementWithDisplayName => { | ||
return ( | ||
(element as ReactElementWithDisplayName)?.type?.displayName !== undefined | ||
) | ||
} | ||
|
||
export function useComposition( | ||
children: React.ReactNode, | ||
component: string | undefined | ||
) { | ||
const Children = React.useMemo( | ||
() => | ||
React.Children.toArray(children).filter((child) => { | ||
if (isElement(child)) return child.type.displayName === component | ||
return false | ||
}), | ||
[children, component] | ||
) | ||
|
||
return Children | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,103 @@ | ||
import * as React from "react" | ||
import { Slot } from "@radix-ui/react-slot" | ||
import { cva, type VariantProps } from "class-variance-authority" | ||
|
||
import { cn } from "@/lib/utils" | ||
import { useComposition } from "@/registry/default/hooks/use-composition" | ||
|
||
interface InputComposition { | ||
Icon: typeof InputIcon | ||
} | ||
|
||
const iconVariants = cva("absolute top-3", { | ||
variants: { | ||
size: { | ||
default: "h-4 w-4", | ||
}, | ||
side: { | ||
left: "left-3", | ||
right: "right-3", | ||
}, | ||
}, | ||
defaultVariants: { | ||
size: "default", | ||
side: "left", | ||
}, | ||
}) | ||
|
||
export interface InputIconProps | ||
extends React.HTMLAttributes<HTMLOrSVGElement>, | ||
VariantProps<typeof iconVariants> {} | ||
|
||
const InputIcon = React.forwardRef<HTMLSlotElement, InputIconProps>( | ||
({ children, className, size, side }, ref) => { | ||
return ( | ||
<Slot | ||
data-icon | ||
ref={ref} | ||
className={cn(iconVariants({ size, side }), className)} | ||
> | ||
{children} | ||
</Slot> | ||
) | ||
} | ||
) | ||
InputIcon.displayName = "InputIcon" | ||
|
||
const inputVariants = cva( | ||
"flex h-10 w-full rounded-md border border-input bg-background 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", | ||
{ | ||
variants: { | ||
composition: { | ||
true: "px-10", | ||
false: "px-3", | ||
}, | ||
}, | ||
defaultVariants: { | ||
composition: false, | ||
}, | ||
} | ||
) | ||
export interface InputProps | ||
extends React.InputHTMLAttributes<HTMLInputElement> {} | ||
|
||
const Input = React.forwardRef<HTMLInputElement, InputProps>( | ||
({ className, type, ...props }, ref) => { | ||
return <input type={type} className={className} ref={ref} {...props} /> | ||
} | ||
) | ||
Input.displayName = "Input" | ||
|
||
const Root = React.forwardRef<HTMLInputElement, InputProps>( | ||
({ children, className, ...props }, ref) => { | ||
const Icons = useComposition(children, InputIcon.displayName) | ||
|
||
if (Icons.length > 0) { | ||
return ( | ||
<div className="relative"> | ||
{Icons} | ||
<Input | ||
ref={ref} | ||
className={cn(inputVariants({ composition: true }), className)} | ||
{...props} | ||
/> | ||
</div> | ||
) | ||
} | ||
return ( | ||
<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", | ||
className | ||
)} | ||
<Input | ||
ref={ref} | ||
className={cn(inputVariants({ composition: false }), className)} | ||
{...props} | ||
/> | ||
) | ||
} | ||
) | ||
Input.displayName = "Input" | ||
) as React.ForwardRefExoticComponent< | ||
InputProps & React.RefAttributes<HTMLInputElement> | ||
> & | ||
InputComposition | ||
|
||
Root.displayName = "Input" | ||
Root.Icon = InputIcon | ||
|
||
export { Input } | ||
export { Root as Input } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { MapIcon, MapPin } from "lucide-react" | ||
|
||
import { Input } from "@/registry/new-york/ui/input" | ||
|
||
export default function InputWithIcons() { | ||
return ( | ||
<div> | ||
<Input> | ||
<Input.Icon side="left"> | ||
<MapIcon /> | ||
</Input.Icon> | ||
<Input.Icon side="right"> | ||
<MapPin /> | ||
</Input.Icon> | ||
</Input> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import * as React from "react" | ||
|
||
type ReactElementWithDisplayName = React.ReactElement & { | ||
type: { displayName: string } | ||
} | ||
|
||
const isElement = ( | ||
element: unknown | undefined | ||
): element is ReactElementWithDisplayName => { | ||
return ( | ||
(element as ReactElementWithDisplayName)?.type?.displayName !== undefined | ||
) | ||
} | ||
|
||
export function useComposition(children: React.ReactNode, component: string) { | ||
const Child = React.useMemo( | ||
() => | ||
React.Children.toArray(children).find((child) => { | ||
if (isElement(child)) return child.type.displayName === component | ||
return false | ||
}), | ||
[children, component] | ||
) | ||
|
||
return Child ?? null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,103 @@ | ||
import * as React from "react" | ||
import { Slot } from "@radix-ui/react-slot" | ||
import { cva, type VariantProps } from "class-variance-authority" | ||
|
||
import { cn } from "@/lib/utils" | ||
import { useComposition } from "@/registry/default/hooks/use-composition" | ||
|
||
interface InputComposition { | ||
Icon: typeof InputIcon | ||
} | ||
|
||
const iconVariants = cva("absolute top-2.5", { | ||
variants: { | ||
size: { | ||
default: "h-4 w-4", | ||
}, | ||
side: { | ||
left: "left-3", | ||
right: "right-3", | ||
}, | ||
}, | ||
defaultVariants: { | ||
size: "default", | ||
side: "left", | ||
}, | ||
}) | ||
|
||
export interface InputIconProps | ||
extends React.HTMLAttributes<HTMLOrSVGElement>, | ||
VariantProps<typeof iconVariants> {} | ||
|
||
const InputIcon = React.forwardRef<HTMLSlotElement, InputIconProps>( | ||
({ children, className, size, side }, ref) => { | ||
return ( | ||
<Slot | ||
data-icon | ||
ref={ref} | ||
className={cn(iconVariants({ size, side }), className)} | ||
> | ||
{children} | ||
</Slot> | ||
) | ||
} | ||
) | ||
InputIcon.displayName = "InputIcon" | ||
|
||
const inputVariants = cva( | ||
"flex h-9 w-full rounded-md border border-input bg-transparent py-1 text-sm shadow-sm transition-colors 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-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", | ||
{ | ||
variants: { | ||
composition: { | ||
true: "px-9", | ||
false: "px-3", | ||
}, | ||
}, | ||
defaultVariants: { | ||
composition: false, | ||
}, | ||
} | ||
) | ||
export interface InputProps | ||
extends React.InputHTMLAttributes<HTMLInputElement> {} | ||
|
||
const Input = React.forwardRef<HTMLInputElement, InputProps>( | ||
({ className, type, ...props }, ref) => { | ||
return <input type={type} className={className} ref={ref} {...props} /> | ||
} | ||
) | ||
Input.displayName = "Input" | ||
|
||
const Root = React.forwardRef<HTMLInputElement, InputProps>( | ||
({ children, className, ...props }, ref) => { | ||
const Icons = useComposition(children, InputIcon.displayName) | ||
|
||
if (Icons.length > 0) { | ||
return ( | ||
<div className="relative"> | ||
{Icons} | ||
<Input | ||
ref={ref} | ||
className={cn(inputVariants({ composition: true }), className)} | ||
{...props} | ||
/> | ||
</div> | ||
) | ||
} | ||
return ( | ||
<input | ||
type={type} | ||
className={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 file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", | ||
className | ||
)} | ||
<Input | ||
ref={ref} | ||
className={cn(inputVariants({ composition: false }), className)} | ||
{...props} | ||
/> | ||
) | ||
} | ||
) | ||
Input.displayName = "Input" | ||
) as React.ForwardRefExoticComponent< | ||
InputProps & React.RefAttributes<HTMLInputElement> | ||
> & | ||
InputComposition | ||
|
||
Root.displayName = "Input" | ||
Root.Icon = InputIcon | ||
|
||
export { Input } | ||
export { Root as Input } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters