From 3995b7523dc9cb5255a10e362d305b73cb3de0ca Mon Sep 17 00:00:00 2001 From: Theo2903 Date: Tue, 29 Apr 2025 14:18:27 +0200 Subject: [PATCH] Add Avatar component to registry - Introduced a new Avatar component for React Native applications, including its implementation in `registry/avatar/avatar.tsx`. - Updated `registry.json` to include the new Avatar component and its dependencies. --- app/(site)/docs/components/avatar/page.tsx | 194 +++++++++++++++++++++ public/r/avatar.json | 18 ++ registry.json | 14 ++ registry/avatar/avatar.tsx | 159 +++++++++++++++++ 4 files changed, 385 insertions(+) create mode 100644 app/(site)/docs/components/avatar/page.tsx create mode 100644 public/r/avatar.json create mode 100644 registry/avatar/avatar.tsx diff --git a/app/(site)/docs/components/avatar/page.tsx b/app/(site)/docs/components/avatar/page.tsx new file mode 100644 index 0000000..468edf1 --- /dev/null +++ b/app/(site)/docs/components/avatar/page.tsx @@ -0,0 +1,194 @@ +import { ComponentPreview } from "@/components/docs/component-preview"; + +export default function AvatarPage() { + return ( + \n Click me\n \n );\n}", + "language": "tsx" + } +]} + componentCode={`import * as React from "react"; +import { + Image, + View, + Text, + ImageSourcePropType, + ImageStyle, +} from "react-native"; +import { cn } from "@/lib/utils"; + +interface AvatarRootProps { + className?: string; + children: React.ReactNode; +} + +const AvatarRoot = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( + + {children} + + ); + } +); + +interface AvatarImageProps { + className?: string; + source: ImageSourcePropType; + alt?: string; + style?: ImageStyle; + onLoad?: () => void; + onError?: () => void; +} + +const AvatarImage = React.forwardRef( + ({ className, source, alt, ...props }, ref) => { + const imageContext = React.useContext(AvatarContext); + + const handleError = () => { + imageContext?.setHasError(true); + props.onError?.(); + }; + + const handleLoad = () => { + imageContext?.setImageLoaded(true); + props.onLoad?.(); + }; + + return ( + + ); + } +); + +interface AvatarFallbackProps { + className?: string; + children: React.ReactNode; + delayMs?: number; +} + +interface AvatarContextValue { + hasError: boolean; + setHasError: React.Dispatch>; + imageLoaded: boolean; + setImageLoaded: React.Dispatch>; +} + +const AvatarContext = React.createContext(null); + +const AvatarFallback = React.forwardRef( + ({ className, children, delayMs = 600, ...props }, ref) => { + const [isShowing, setIsShowing] = React.useState(delayMs === 0); + const avatarContext = React.useContext(AvatarContext); + + React.useEffect(() => { + if (delayMs === 0) return; + + // Only show fallback if image has error or hasn't loaded after delay + const timer = setTimeout(() => { + if (!avatarContext?.imageLoaded) { + setIsShowing(true); + } + }, delayMs); + + return () => clearTimeout(timer); + }, [delayMs, avatarContext?.imageLoaded]); + + // Hide fallback if image loads successfully + React.useEffect(() => { + if (avatarContext?.imageLoaded) { + setIsShowing(false); + } + }, [avatarContext?.imageLoaded]); + + if (!isShowing || avatarContext?.imageLoaded) { + return null; + } + + return ( + + {typeof children === "string" ? ( + + {children} + + ) : ( + children + )} + + ); + } +); + +// Wrap the original AvatarRoot to provide context +const Avatar = React.forwardRef((props, ref) => { + const [hasError, setHasError] = React.useState(false); + const [imageLoaded, setImageLoaded] = React.useState(false); + + return ( + + + + ); +}); + +AvatarRoot.displayName = "AvatarRoot"; +Avatar.displayName = "Avatar"; +AvatarImage.displayName = "AvatarImage"; +AvatarFallback.displayName = "AvatarFallback"; + +export { Avatar, AvatarImage, AvatarFallback }; +`} + previewCode={`import { Avatar } from "@nativeui/ui"; + +export default function AvatarDemo() { + return ( +
+ Default Avatar + Delete + Outline + Secondary + Ghost + Link +
+ ); +}`} + registryName="avatar" + packageName="@nativeui/ui" + /> + ); +} diff --git a/public/r/avatar.json b/public/r/avatar.json new file mode 100644 index 0000000..ccae044 --- /dev/null +++ b/public/r/avatar.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "avatar", + "type": "registry:component", + "title": "Avatar", + "description": "An avatar component for React Native applications.", + "dependencies": [ + "react-native" + ], + "registryDependencies": [], + "files": [ + { + "path": "registry/avatar/avatar.tsx", + "content": "import * as React from \"react\";\nimport {\n Image,\n View,\n Text,\n ImageSourcePropType,\n ImageStyle,\n} from \"react-native\";\nimport { cn } from \"@/lib/utils\";\n\ninterface AvatarRootProps {\n className?: string;\n children: React.ReactNode;\n}\n\nconst AvatarRoot = React.forwardRef(\n ({ className, children, ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n\ninterface AvatarImageProps {\n className?: string;\n source: ImageSourcePropType;\n alt?: string;\n style?: ImageStyle;\n onLoad?: () => void;\n onError?: () => void;\n}\n\nconst AvatarImage = React.forwardRef(\n ({ className, source, alt, ...props }, ref) => {\n const imageContext = React.useContext(AvatarContext);\n\n const handleError = () => {\n imageContext?.setHasError(true);\n props.onError?.();\n };\n\n const handleLoad = () => {\n imageContext?.setImageLoaded(true);\n props.onLoad?.();\n };\n\n return (\n \n );\n }\n);\n\ninterface AvatarFallbackProps {\n className?: string;\n children: React.ReactNode;\n delayMs?: number;\n}\n\ninterface AvatarContextValue {\n hasError: boolean;\n setHasError: React.Dispatch>;\n imageLoaded: boolean;\n setImageLoaded: React.Dispatch>;\n}\n\nconst AvatarContext = React.createContext(null);\n\nconst AvatarFallback = React.forwardRef(\n ({ className, children, delayMs = 600, ...props }, ref) => {\n const [isShowing, setIsShowing] = React.useState(delayMs === 0);\n const avatarContext = React.useContext(AvatarContext);\n\n React.useEffect(() => {\n if (delayMs === 0) return;\n\n // Only show fallback if image has error or hasn't loaded after delay\n const timer = setTimeout(() => {\n if (!avatarContext?.imageLoaded) {\n setIsShowing(true);\n }\n }, delayMs);\n\n return () => clearTimeout(timer);\n }, [delayMs, avatarContext?.imageLoaded]);\n\n // Hide fallback if image loads successfully\n React.useEffect(() => {\n if (avatarContext?.imageLoaded) {\n setIsShowing(false);\n }\n }, [avatarContext?.imageLoaded]);\n\n if (!isShowing || avatarContext?.imageLoaded) {\n return null;\n }\n\n return (\n \n {typeof children === \"string\" ? (\n \n {children}\n \n ) : (\n children\n )}\n \n );\n }\n);\n\n// Wrap the original AvatarRoot to provide context\nconst Avatar = React.forwardRef((props, ref) => {\n const [hasError, setHasError] = React.useState(false);\n const [imageLoaded, setImageLoaded] = React.useState(false);\n\n return (\n \n \n \n );\n});\n\nAvatarRoot.displayName = \"AvatarRoot\";\nAvatar.displayName = \"Avatar\";\nAvatarImage.displayName = \"AvatarImage\";\nAvatarFallback.displayName = \"AvatarFallback\";\n\nexport { Avatar, AvatarImage, AvatarFallback };\n", + "type": "registry:component" + } + ] +} \ No newline at end of file diff --git a/registry.json b/registry.json index 6b1ec35..6f7d236 100644 --- a/registry.json +++ b/registry.json @@ -44,6 +44,20 @@ ], "dependencies": ["react-native", "class-variance-authority"], "registryDependencies": [] + }, + { + "name": "avatar", + "type": "registry:component", + "title": "Avatar", + "description": "An avatar component for React Native applications.", + "files": [ + { + "path": "registry/avatar/avatar.tsx", + "type": "registry:component" + } + ], + "dependencies": ["react-native"], + "registryDependencies": [] } ] } diff --git a/registry/avatar/avatar.tsx b/registry/avatar/avatar.tsx new file mode 100644 index 0000000..a1517e6 --- /dev/null +++ b/registry/avatar/avatar.tsx @@ -0,0 +1,159 @@ +import * as React from "react"; +import { + Image, + View, + Text, + ImageSourcePropType, + ImageStyle, +} from "react-native"; +import { cn } from "@/lib/utils"; + +interface AvatarRootProps { + className?: string; + children: React.ReactNode; +} + +const AvatarRoot = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( + + {children} + + ); + } +); + +interface AvatarImageProps { + className?: string; + source: ImageSourcePropType; + alt?: string; + style?: ImageStyle; + onLoad?: () => void; + onError?: () => void; +} + +const AvatarImage = React.forwardRef( + ({ className, source, alt, ...props }, ref) => { + const imageContext = React.useContext(AvatarContext); + + const handleError = () => { + imageContext?.setHasError(true); + props.onError?.(); + }; + + const handleLoad = () => { + imageContext?.setImageLoaded(true); + props.onLoad?.(); + }; + + return ( + + ); + } +); + +interface AvatarFallbackProps { + className?: string; + children: React.ReactNode; + delayMs?: number; +} + +interface AvatarContextValue { + hasError: boolean; + setHasError: React.Dispatch>; + imageLoaded: boolean; + setImageLoaded: React.Dispatch>; +} + +const AvatarContext = React.createContext(null); + +const AvatarFallback = React.forwardRef( + ({ className, children, delayMs = 600, ...props }, ref) => { + const [isShowing, setIsShowing] = React.useState(delayMs === 0); + const avatarContext = React.useContext(AvatarContext); + + React.useEffect(() => { + if (delayMs === 0) return; + + // Only show fallback if image has error or hasn't loaded after delay + const timer = setTimeout(() => { + if (!avatarContext?.imageLoaded) { + setIsShowing(true); + } + }, delayMs); + + return () => clearTimeout(timer); + }, [delayMs, avatarContext?.imageLoaded]); + + // Hide fallback if image loads successfully + React.useEffect(() => { + if (avatarContext?.imageLoaded) { + setIsShowing(false); + } + }, [avatarContext?.imageLoaded]); + + if (!isShowing || avatarContext?.imageLoaded) { + return null; + } + + return ( + + {typeof children === "string" ? ( + + {children} + + ) : ( + children + )} + + ); + } +); + +// Wrap the original AvatarRoot to provide context +const Avatar = React.forwardRef((props, ref) => { + const [hasError, setHasError] = React.useState(false); + const [imageLoaded, setImageLoaded] = React.useState(false); + + return ( + + + + ); +}); + +AvatarRoot.displayName = "AvatarRoot"; +Avatar.displayName = "Avatar"; +AvatarImage.displayName = "AvatarImage"; +AvatarFallback.displayName = "AvatarFallback"; + +export { Avatar, AvatarImage, AvatarFallback };