diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 0732245377a..b153c45a6df 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -324,7 +324,7 @@ export const Index: Record = { name: "input", description: "", type: "registry:ui", - registryDependencies: undefined, + registryDependencies: ["use-composition"], files: [{ path: "registry/new-york/ui/input.tsx", type: "registry:ui", @@ -2329,6 +2329,21 @@ export const Index: Record = { 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: "", @@ -4095,15 +4110,14 @@ export const Index: Record = { 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", @@ -5595,7 +5609,7 @@ export const Index: Record = { name: "input", description: "", type: "registry:ui", - registryDependencies: undefined, + registryDependencies: ["use-composition"], files: [{ path: "registry/default/ui/input.tsx", type: "registry:ui", @@ -7600,6 +7614,21 @@ export const Index: Record = { 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: "", @@ -9366,15 +9395,14 @@ export const Index: Record = { 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", diff --git a/apps/www/public/r/index.json b/apps/www/public/r/index.json index 590d2f98ca6..75323a2ec69 100644 --- a/apps/www/public/r/index.json +++ b/apps/www/public/r/index.json @@ -322,6 +322,9 @@ { "name": "input", "type": "registry:ui", + "registryDependencies": [ + "use-composition" + ], "files": [ { "path": "ui/input.tsx", diff --git a/apps/www/public/r/styles/default/input-with-icons.json b/apps/www/public/r/styles/default/input-with-icons.json index d8d8ccc9916..74d79478a82 100644 --- a/apps/www/public/r/styles/default/input-with-icons.json +++ b/apps/www/public/r/styles/default/input-with-icons.json @@ -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
\n \n \n \n \n \n \n \n \n
\n )\n}\n", "type": "registry:example", "target": "" diff --git a/apps/www/public/r/styles/default/input.json b/apps/www/public/r/styles/default/input.json index 865f74f5a6e..e336866f57f 100644 --- a/apps/www/public/r/styles/default/input.json +++ b/apps/www/public/r/styles/default/input.json @@ -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>(\n ({ className, type, ...props }, ref) => {\n return (\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,\n VariantProps {}\n\nconst InputIcon = React.forwardRef(\n ({ children, className, size, side }, ref) => {\n return (\n \n {children}\n \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 {}\n\nconst Input = React.forwardRef(\n ({ className, type, ...props }, ref) => {\n return \n }\n)\nInput.displayName = \"Input\"\n\nconst Root = React.forwardRef(\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
\n {Icons}\n \n
\n )\n }\n return (\n \n )\n }\n) as React.ForwardRefExoticComponent<\n InputProps & React.RefAttributes\n> &\n InputComposition\n\nRoot.displayName = \"Input\"\nRoot.Icon = InputIcon\n\nexport { Root as Input }\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/public/r/styles/default/use-composition.json b/apps/www/public/r/styles/default/use-composition.json new file mode 100644 index 00000000000..774d99e152d --- /dev/null +++ b/apps/www/public/r/styles/default/use-composition.json @@ -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 {\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\n\n return Children ?? []\n}\n", + "type": "registry:hook", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/input-with-icons.json b/apps/www/public/r/styles/new-york/input-with-icons.json index ea28396bd6b..67a3ccba6c1 100644 --- a/apps/www/public/r/styles/new-york/input-with-icons.json +++ b/apps/www/public/r/styles/new-york/input-with-icons.json @@ -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
\n \n \n \n \n \n \n \n \n
\n )\n}\n", "type": "registry:example", "target": "" diff --git a/apps/www/public/r/styles/new-york/input.json b/apps/www/public/r/styles/new-york/input.json index d083cd60ab2..73a20873045 100644 --- a/apps/www/public/r/styles/new-york/input.json +++ b/apps/www/public/r/styles/new-york/input.json @@ -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>(\n ({ className, type, ...props }, ref) => {\n return (\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,\n VariantProps {}\n\nconst InputIcon = React.forwardRef(\n ({ children, className, size, side }, ref) => {\n return (\n \n {children}\n \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 {}\n\nconst Input = React.forwardRef(\n ({ className, type, ...props }, ref) => {\n return \n }\n)\nInput.displayName = \"Input\"\n\nconst Root = React.forwardRef(\n ({ children, className, ...props }, ref) => {\n const Icons = useComposition(children, InputIcon.displayName!)\n\n if (Icons.length > 0) {\n return (\n
\n {Icons}\n \n
\n )\n }\n return (\n \n )\n }\n) as React.ForwardRefExoticComponent<\n InputProps & React.RefAttributes\n> &\n InputComposition\n\nRoot.displayName = \"Input\"\nRoot.Icon = InputIcon\n\nexport { Root as Input }\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/public/r/styles/new-york/use-composition.json b/apps/www/public/r/styles/new-york/use-composition.json new file mode 100644 index 00000000000..774d99e152d --- /dev/null +++ b/apps/www/public/r/styles/new-york/use-composition.json @@ -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 {\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\n\n return Children ?? []\n}\n", + "type": "registry:hook", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/registry/default/examples/input-with-icons.tsx b/apps/www/registry/default/examples/input-with-icons.tsx index 8f160e79941..006b2aef35d 100644 --- a/apps/www/registry/default/examples/input-with-icons.tsx +++ b/apps/www/registry/default/examples/input-with-icons.tsx @@ -4,7 +4,7 @@ import { Input } from "@/registry/default/ui/input" export default function InputWithIcons() { return ( -
+
@@ -13,6 +13,16 @@ export default function InputWithIcons() { + + + + + + + + + +
) } diff --git a/apps/www/registry/default/hooks/use-composition.tsx b/apps/www/registry/default/hooks/use-composition.tsx index e391091443a..4a6306f8b05 100644 --- a/apps/www/registry/default/hooks/use-composition.tsx +++ b/apps/www/registry/default/hooks/use-composition.tsx @@ -14,8 +14,8 @@ const isElement = ( export function useComposition( children: React.ReactNode, - component: string | undefined -) { + component: string +): Array { const Children = React.useMemo( () => React.Children.toArray(children).filter((child) => { @@ -23,7 +23,8 @@ export function useComposition( return false }), [children, component] - ) + ) as Array - return Children + console.log({ Children }) + return Children ?? [] } diff --git a/apps/www/registry/default/ui/input.tsx b/apps/www/registry/default/ui/input.tsx index acea98df419..11af5f668cd 100644 --- a/apps/www/registry/default/ui/input.tsx +++ b/apps/www/registry/default/ui/input.tsx @@ -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, }, } ) @@ -70,26 +73,38 @@ Input.displayName = "Input" const Root = React.forwardRef( ({ 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 (
{Icons}
) } return ( - + ) } ) as React.ForwardRefExoticComponent< diff --git a/apps/www/registry/new-york/examples/input-with-icons.tsx b/apps/www/registry/new-york/examples/input-with-icons.tsx index 049488760d9..6f1b5838375 100644 --- a/apps/www/registry/new-york/examples/input-with-icons.tsx +++ b/apps/www/registry/new-york/examples/input-with-icons.tsx @@ -4,7 +4,7 @@ import { Input } from "@/registry/new-york/ui/input" export default function InputWithIcons() { return ( -
+
@@ -13,6 +13,16 @@ export default function InputWithIcons() { + + + + + + + + + +
) } diff --git a/apps/www/registry/new-york/hooks/use-composition.tsx b/apps/www/registry/new-york/hooks/use-composition.tsx index 5e43f537606..915ce485779 100644 --- a/apps/www/registry/new-york/hooks/use-composition.tsx +++ b/apps/www/registry/new-york/hooks/use-composition.tsx @@ -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 { + 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 - return Child ?? null + return Children ?? [] } diff --git a/apps/www/registry/new-york/ui/input.tsx b/apps/www/registry/new-york/ui/input.tsx index bb98557c377..d668dddf080 100644 --- a/apps/www/registry/new-york/ui/input.tsx +++ b/apps/www/registry/new-york/ui/input.tsx @@ -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 @@ -70,7 +70,7 @@ Input.displayName = "Input" const Root = React.forwardRef( ({ children, className, ...props }, ref) => { - const Icons = useComposition(children, InputIcon.displayName) + const Icons = useComposition(children, InputIcon.displayName!) if (Icons.length > 0) { return ( diff --git a/apps/www/registry/registry-examples.ts b/apps/www/registry/registry-examples.ts index c09ecf7bf7d..b32a04f3222 100644 --- a/apps/www/registry/registry-examples.ts +++ b/apps/www/registry/registry-examples.ts @@ -781,7 +781,7 @@ export const examples: Registry = [ registryDependencies: ["input"], files: [ { - path: "example/input-with-icons.tsx", + path: "examples/input-with-icons.tsx", type: "registry:example", }, ], diff --git a/apps/www/registry/registry-hooks.ts b/apps/www/registry/registry-hooks.ts index 04a0b0a92a6..4337cabb0c9 100644 --- a/apps/www/registry/registry-hooks.ts +++ b/apps/www/registry/registry-hooks.ts @@ -21,4 +21,14 @@ export const hooks: Registry = [ }, ], }, + { + name: "use-composition", + type: "registry:hook", + files: [ + { + path: "hooks/use-composition.tsx", + type: "registry:hook", + }, + ], + }, ] diff --git a/apps/www/registry/registry-ui.ts b/apps/www/registry/registry-ui.ts index 4870f3abb1c..4c83379aebc 100644 --- a/apps/www/registry/registry-ui.ts +++ b/apps/www/registry/registry-ui.ts @@ -266,6 +266,7 @@ export const ui: Registry = [ { name: "input", type: "registry:ui", + registryDependencies: ["use-composition"], files: [ { path: "ui/input.tsx",