Skip to content

Commit

Permalink
fix: spacing on both sides when only 1 side is present
Browse files Browse the repository at this point in the history
  • Loading branch information
IndyVC committed Jan 2, 2025
1 parent 65461a7 commit d59d8c8
Show file tree
Hide file tree
Showing 17 changed files with 153 additions and 42 deletions.
52 changes: 40 additions & 12 deletions apps/www/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ export const Index: Record<string, any> = {
name: "input",
description: "",
type: "registry:ui",
registryDependencies: undefined,
registryDependencies: ["use-composition"],
files: [{
path: "registry/new-york/ui/input.tsx",
type: "registry:ui",
Expand Down Expand Up @@ -2329,6 +2329,21 @@ export const Index: Record<string, any> = {
source: "",
meta: undefined,
},
"use-composition": {
name: "use-composition",
description: "",
type: "registry:hook",
registryDependencies: undefined,
files: [{
path: "registry/new-york/hooks/use-composition.tsx",
type: "registry:hook",
target: ""
}],
categories: undefined,
component: React.lazy(() => import("@/registry/new-york/hooks/use-composition.tsx")),
source: "",
meta: undefined,
},
"sink": {
name: "sink",
description: "",
Expand Down Expand Up @@ -4095,15 +4110,14 @@ export const Index: Record<string, any> = {
type: "registry:example",
registryDependencies: ["input"],
files: [{
path: "registry/new-york/example/input-with-icons.tsx",
path: "registry/new-york/examples/input-with-icons.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(() => import("@/registry/new-york/example/input-with-icons.tsx")),
categories: undefined,
component: React.lazy(() => import("@/registry/new-york/examples/input-with-icons.tsx")),
source: "",
category: "",
subcategory: "",
chunks: []
meta: undefined,
},
"input-otp-demo": {
name: "input-otp-demo",
Expand Down Expand Up @@ -5595,7 +5609,7 @@ export const Index: Record<string, any> = {
name: "input",
description: "",
type: "registry:ui",
registryDependencies: undefined,
registryDependencies: ["use-composition"],
files: [{
path: "registry/default/ui/input.tsx",
type: "registry:ui",
Expand Down Expand Up @@ -7600,6 +7614,21 @@ export const Index: Record<string, any> = {
source: "",
meta: undefined,
},
"use-composition": {
name: "use-composition",
description: "",
type: "registry:hook",
registryDependencies: undefined,
files: [{
path: "registry/default/hooks/use-composition.tsx",
type: "registry:hook",
target: ""
}],
categories: undefined,
component: React.lazy(() => import("@/registry/default/hooks/use-composition.tsx")),
source: "",
meta: undefined,
},
"sink": {
name: "sink",
description: "",
Expand Down Expand Up @@ -9366,15 +9395,14 @@ export const Index: Record<string, any> = {
type: "registry:example",
registryDependencies: ["input"],
files: [{
path: "registry/default/example/input-with-icons.tsx",
path: "registry/default/examples/input-with-icons.tsx",
type: "registry:example",
target: ""
}],
component: React.lazy(() => import("@/registry/default/example/input-with-icons.tsx")),
categories: undefined,
component: React.lazy(() => import("@/registry/default/examples/input-with-icons.tsx")),
source: "",
category: "",
subcategory: "",
chunks: []
meta: undefined,
},
"input-otp-demo": {
name: "input-otp-demo",
Expand Down
3 changes: 3 additions & 0 deletions apps/www/public/r/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@
{
"name": "input",
"type": "registry:ui",
"registryDependencies": [
"use-composition"
],
"files": [
{
"path": "ui/input.tsx",
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/styles/default/input-with-icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
],
"files": [
{
"path": "example/input-with-icons.tsx",
"path": "examples/input-with-icons.tsx",
"content": "import { MapIcon, MapPin } from \"lucide-react\"\n\nimport { Input } from \"@/registry/default/ui/input\"\n\nexport default function InputWithIcons() {\n return (\n <div>\n <Input>\n <Input.Icon side=\"left\">\n <MapIcon />\n </Input.Icon>\n <Input.Icon side=\"right\">\n <MapPin />\n </Input.Icon>\n </Input>\n </div>\n )\n}\n",
"type": "registry:example",
"target": ""
Expand Down
5 changes: 4 additions & 1 deletion apps/www/public/r/styles/default/input.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"name": "input",
"type": "registry:ui",
"registryDependencies": [
"use-composition"
],
"files": [
{
"path": "ui/input.tsx",
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base 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 md:text-sm\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
"content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useComposition } from \"@/registry/default/hooks/use-composition\"\n\ninterface InputComposition {\n Icon: typeof InputIcon\n}\n\nconst iconVariants = cva(\"absolute top-3\", {\n variants: {\n size: {\n default: \"h-4 w-4\",\n },\n side: {\n left: \"left-3\",\n right: \"right-3\",\n },\n },\n defaultVariants: {\n size: \"default\",\n side: \"left\",\n },\n})\n\nexport interface InputIconProps\n extends React.HTMLAttributes<HTMLOrSVGElement>,\n VariantProps<typeof iconVariants> {}\n\nconst InputIcon = React.forwardRef<HTMLSlotElement, InputIconProps>(\n ({ children, className, size, side }, ref) => {\n return (\n <Slot\n data-icon\n ref={ref}\n className={cn(iconVariants({ size, side }), className)}\n >\n {children}\n </Slot>\n )\n }\n)\nInputIcon.displayName = \"InputIcon\"\n\nconst inputVariants = cva(\n \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base 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 md:text-sm\",\n {\n variants: {\n left: {\n true: \"pl-10\",\n },\n right: {\n true: \"pr-10\",\n },\n },\n defaultVariants: {\n left: false,\n right: false,\n },\n }\n)\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return <input type={type} className={className} ref={ref} {...props} />\n }\n)\nInput.displayName = \"Input\"\n\nconst Root = React.forwardRef<HTMLInputElement, InputProps>(\n ({ children, className, ...props }, ref) => {\n const Icons = useComposition(children, InputIcon.displayName!)\n const hasLeftIcon = React.useMemo(\n () =>\n Icons.some(\n (icon) => (icon as React.ReactElement).props.side === \"left\"\n ),\n [Icons]\n )\n const hasRightIcon = React.useMemo(\n () =>\n Icons.some(\n (icon) => (icon as React.ReactElement).props.side === \"right\"\n ),\n [Icons]\n )\n if (Icons.length > 0) {\n return (\n <div className=\"relative\">\n {Icons}\n <Input\n ref={ref}\n className={cn(\n inputVariants({ left: hasLeftIcon, right: hasRightIcon }),\n className\n )}\n {...props}\n />\n </div>\n )\n }\n return (\n <Input ref={ref} className={cn(inputVariants(), className)} {...props} />\n )\n }\n) as React.ForwardRefExoticComponent<\n InputProps & React.RefAttributes<HTMLInputElement>\n> &\n InputComposition\n\nRoot.displayName = \"Input\"\nRoot.Icon = InputIcon\n\nexport { Root as Input }\n",
"type": "registry:ui",
"target": ""
}
Expand Down
12 changes: 12 additions & 0 deletions apps/www/public/r/styles/default/use-composition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "use-composition",
"type": "registry:hook",
"files": [
{
"path": "hooks/use-composition.tsx",
"content": "import * as React from \"react\"\n\ntype ReactElementWithDisplayName = React.ReactElement & {\n type: { displayName: string }\n}\n\nconst isElement = (\n element: unknown | undefined\n): element is ReactElementWithDisplayName => {\n return (\n (element as ReactElementWithDisplayName)?.type?.displayName !== undefined\n )\n}\n\nexport function useComposition(\n children: React.ReactNode,\n component: string\n): Array<React.ReactNode> {\n const Children = React.useMemo(\n () =>\n React.Children.toArray(children).find((child) => {\n if (isElement(child)) return child.type.displayName === component\n return false\n }),\n [children, component]\n ) as Array<React.ReactNode>\n\n return Children ?? []\n}\n",
"type": "registry:hook",
"target": ""
}
]
}
2 changes: 1 addition & 1 deletion apps/www/public/r/styles/new-york/input-with-icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
],
"files": [
{
"path": "example/input-with-icons.tsx",
"path": "examples/input-with-icons.tsx",
"content": "import { MapIcon, MapPin } from \"lucide-react\"\n\nimport { Input } from \"@/registry/new-york/ui/input\"\n\nexport default function InputWithIcons() {\n return (\n <div>\n <Input>\n <Input.Icon side=\"left\">\n <MapIcon />\n </Input.Icon>\n <Input.Icon side=\"right\">\n <MapPin />\n </Input.Icon>\n </Input>\n </div>\n )\n}\n",
"type": "registry:example",
"target": ""
Expand Down
5 changes: 4 additions & 1 deletion apps/www/public/r/styles/new-york/input.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"name": "input",
"type": "registry:ui",
"registryDependencies": [
"use-composition"
],
"files": [
{
"path": "ui/input.tsx",
"content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base 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 md:text-sm\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n",
"content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useComposition } from \"@/registry/new-york/hooks/use-composition\"\n\ninterface InputComposition {\n Icon: typeof InputIcon\n}\n\nconst iconVariants = cva(\"absolute top-2.5\", {\n variants: {\n size: {\n default: \"h-4 w-4\",\n },\n side: {\n left: \"left-3\",\n right: \"right-3\",\n },\n },\n defaultVariants: {\n size: \"default\",\n side: \"left\",\n },\n})\n\nexport interface InputIconProps\n extends React.HTMLAttributes<HTMLOrSVGElement>,\n VariantProps<typeof iconVariants> {}\n\nconst InputIcon = React.forwardRef<HTMLSlotElement, InputIconProps>(\n ({ children, className, size, side }, ref) => {\n return (\n <Slot\n data-icon\n ref={ref}\n className={cn(iconVariants({ size, side }), className)}\n >\n {children}\n </Slot>\n )\n }\n)\nInputIcon.displayName = \"InputIcon\"\n\nconst inputVariants = cva(\n \"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base 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 md:text-sm\",\n {\n variants: {\n composition: {\n true: \"px-9\",\n false: \"px-3\",\n },\n },\n defaultVariants: {\n composition: false,\n },\n }\n)\nexport interface InputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => {\n return <input type={type} className={className} ref={ref} {...props} />\n }\n)\nInput.displayName = \"Input\"\n\nconst Root = React.forwardRef<HTMLInputElement, InputProps>(\n ({ children, className, ...props }, ref) => {\n const Icons = useComposition(children, InputIcon.displayName!)\n\n if (Icons.length > 0) {\n return (\n <div className=\"relative\">\n {Icons}\n <Input\n ref={ref}\n className={cn(inputVariants({ composition: true }), className)}\n {...props}\n />\n </div>\n )\n }\n return (\n <Input\n ref={ref}\n className={cn(inputVariants({ composition: false }), className)}\n {...props}\n />\n )\n }\n) as React.ForwardRefExoticComponent<\n InputProps & React.RefAttributes<HTMLInputElement>\n> &\n InputComposition\n\nRoot.displayName = \"Input\"\nRoot.Icon = InputIcon\n\nexport { Root as Input }\n",
"type": "registry:ui",
"target": ""
}
Expand Down
12 changes: 12 additions & 0 deletions apps/www/public/r/styles/new-york/use-composition.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "use-composition",
"type": "registry:hook",
"files": [
{
"path": "hooks/use-composition.tsx",
"content": "import * as React from \"react\"\n\ntype ReactElementWithDisplayName = React.ReactElement & {\n type: { displayName: string }\n}\n\nconst isElement = (\n element: unknown | undefined\n): element is ReactElementWithDisplayName => {\n return (\n (element as ReactElementWithDisplayName)?.type?.displayName !== undefined\n )\n}\n\nexport function useComposition(\n children: React.ReactNode,\n component: string\n): Array<React.ReactNode> {\n const Children = React.useMemo(\n () =>\n React.Children.toArray(children).find((child) => {\n if (isElement(child)) return child.type.displayName === component\n return false\n }),\n [children, component]\n ) as Array<React.ReactNode>\n\n return Children ?? []\n}\n",
"type": "registry:hook",
"target": ""
}
]
}
12 changes: 11 additions & 1 deletion apps/www/registry/default/examples/input-with-icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from "@/registry/default/ui/input"

export default function InputWithIcons() {
return (
<div>
<div className="flex flex-col gap-4">
<Input>
<Input.Icon side="left">
<MapIcon />
Expand All @@ -13,6 +13,16 @@ export default function InputWithIcons() {
<MapPin />
</Input.Icon>
</Input>
<Input>
<Input.Icon side="left">
<MapIcon />
</Input.Icon>
</Input>
<Input>
<Input.Icon side="right">
<MapIcon />
</Input.Icon>
</Input>
</div>
)
}
9 changes: 5 additions & 4 deletions apps/www/registry/default/hooks/use-composition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ const isElement = (

export function useComposition(
children: React.ReactNode,
component: string | undefined
) {
component: string
): Array<React.ReactNode> {
const Children = React.useMemo(
() =>
React.Children.toArray(children).filter((child) => {
if (isElement(child)) return child.type.displayName === component
return false
}),
[children, component]
)
) as Array<React.ReactNode>

return Children
console.log({ Children })
return Children ?? []
}
39 changes: 27 additions & 12 deletions apps/www/registry/default/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ const inputVariants = cva(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base 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 md:text-sm",
{
variants: {
composition: {
true: "px-10",
false: "px-3",
left: {
true: "pl-10",
},
right: {
true: "pr-10",
},
},
defaultVariants: {
composition: false,
left: false,
right: false,
},
}
)
Expand All @@ -70,26 +73,38 @@ Input.displayName = "Input"

const Root = React.forwardRef<HTMLInputElement, InputProps>(
({ children, className, ...props }, ref) => {
const Icons = useComposition(children, InputIcon.displayName)

const Icons = useComposition(children, InputIcon.displayName!)
const hasLeftIcon = React.useMemo(
() =>
Icons.some(
(icon) => (icon as React.ReactElement).props.side === "left"
),
[Icons]
)
const hasRightIcon = React.useMemo(
() =>
Icons.some(
(icon) => (icon as React.ReactElement).props.side === "right"
),
[Icons]
)
if (Icons.length > 0) {
return (
<div className="relative">
{Icons}
<Input
ref={ref}
className={cn(inputVariants({ composition: true }), className)}
className={cn(
inputVariants({ left: hasLeftIcon, right: hasRightIcon }),
className
)}
{...props}
/>
</div>
)
}
return (
<Input
ref={ref}
className={cn(inputVariants({ composition: false }), className)}
{...props}
/>
<Input ref={ref} className={cn(inputVariants(), className)} {...props} />
)
}
) as React.ForwardRefExoticComponent<
Expand Down
12 changes: 11 additions & 1 deletion apps/www/registry/new-york/examples/input-with-icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from "@/registry/new-york/ui/input"

export default function InputWithIcons() {
return (
<div>
<div className="flex flex-col gap-4">
<Input>
<Input.Icon side="left">
<MapIcon />
Expand All @@ -13,6 +13,16 @@ export default function InputWithIcons() {
<MapPin />
</Input.Icon>
</Input>
<Input>
<Input.Icon side="left">
<MapIcon />
</Input.Icon>
</Input>
<Input>
<Input.Icon side="right">
<MapIcon />
</Input.Icon>
</Input>
</div>
)
}
13 changes: 8 additions & 5 deletions apps/www/registry/new-york/hooks/use-composition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ const isElement = (
)
}

export function useComposition(children: React.ReactNode, component: string) {
const Child = React.useMemo(
export function useComposition(
children: React.ReactNode,
component: string
): Array<React.ReactNode> {
const Children = React.useMemo(
() =>
React.Children.toArray(children).find((child) => {
React.Children.toArray(children).filter((child) => {
if (isElement(child)) return child.type.displayName === component
return false
}),
[children, component]
)
) as Array<React.ReactNode>

return Child ?? null
return Children ?? []
}
4 changes: 2 additions & 2 deletions apps/www/registry/new-york/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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"
import { useComposition } from "@/registry/new-york/hooks/use-composition"

interface InputComposition {
Icon: typeof InputIcon
Expand Down Expand Up @@ -70,7 +70,7 @@ Input.displayName = "Input"

const Root = React.forwardRef<HTMLInputElement, InputProps>(
({ children, className, ...props }, ref) => {
const Icons = useComposition(children, InputIcon.displayName)
const Icons = useComposition(children, InputIcon.displayName!)

if (Icons.length > 0) {
return (
Expand Down
Loading

0 comments on commit d59d8c8

Please sign in to comment.