diff --git a/app/(site)/docs/components/dialog/page.tsx b/app/(site)/docs/components/dialog/page.tsx new file mode 100644 index 0000000..2973796 --- /dev/null +++ b/app/(site)/docs/components/dialog/page.tsx @@ -0,0 +1,387 @@ +import { ComponentPreview } from "@/components/docs/component-preview"; + +export default function DialogPage() { + return ( + \n Click me\n \n );\n}", + "language": "tsx" + } +]} + componentCode={`import * as React from "react"; +import { + View, + Text, + Pressable, + Modal, + TouchableWithoutFeedback, + Platform, + Animated, + Dimensions, + KeyboardAvoidingView, + ScrollView, +} from "react-native"; +import { cn } from "@/lib/utils"; +import { Ionicons } from "@expo/vector-icons"; + +interface DialogProps { + children: React.ReactNode; + className?: string; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +interface DialogTriggerProps { + children: React.ReactNode; + className?: string; + disabled?: boolean; + asChild?: boolean; +} + +interface DialogContentProps { + children: React.ReactNode; + className?: string; + showCloseButton?: boolean; + onInteractOutside?: () => void; +} + +interface DialogHeaderProps { + className?: string; + children: React.ReactNode; +} + +interface DialogFooterProps { + className?: string; + children: React.ReactNode; +} + +interface DialogTitleProps { + className?: string; + children: React.ReactNode; +} + +interface DialogDescriptionProps { + className?: string; + children: React.ReactNode; +} + +interface DialogCloseProps { + children: React.ReactElement<{ onPress?: (e: any) => void }>; + className?: string; +} + +const DialogContext = React.createContext<{ + open: boolean; + setOpen: (open: boolean) => void; + handleClose?: () => void; +}>({ + open: false, + setOpen: () => {}, +}); + +const Dialog = React.forwardRef( + ({ children, className, open = false, onOpenChange, ...props }, ref) => { + const [internalOpen, setInternalOpen] = React.useState(false); + + const isControlled = open !== undefined; + const isOpen = isControlled ? open : internalOpen; + + const setOpen = React.useCallback( + (value: boolean) => { + if (!isControlled) { + setInternalOpen(value); + } + onOpenChange?.(value); + }, + [isControlled, onOpenChange] + ); + + return ( + + + {children} + + + ); + } +); + +Dialog.displayName = "Dialog"; + +const DialogTrigger = React.forwardRef( + ( + { children, className, disabled = false, asChild = false, ...props }, + ref + ) => { + const { setOpen } = React.useContext(DialogContext); + + if (asChild) { + const child = React.Children.only(children) as React.ReactElement<{ + onPress?: (e: any) => void; + ref?: React.Ref; + disabled?: boolean; + }>; + return React.cloneElement(child, { + ...props, + ref, + onPress: (e: any) => { + child.props?.onPress?.(e); + setOpen(true); + }, + disabled, + }); + } + + return ( + setOpen(true)} + accessibilityRole="button" + {...props} + > + {children} + + ); + } +); + +DialogTrigger.displayName = "DialogTrigger"; + +const DialogContent = React.forwardRef( + ( + { + children, + className, + showCloseButton = true, + onInteractOutside, + ...props + }, + ref + ) => { + const { open, setOpen } = React.useContext(DialogContext); + const fadeAnim = React.useRef(new Animated.Value(0)).current; + const scaleAnim = React.useRef(new Animated.Value(0.95)).current; + const { height: SCREEN_HEIGHT } = Dimensions.get("window"); + const [isVisible, setIsVisible] = React.useState(open); + + React.useEffect(() => { + if (open && !isVisible) { + setIsVisible(true); + } + }, [open, isVisible]); + + React.useEffect(() => { + if (isVisible) { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + Animated.spring(scaleAnim, { + toValue: 1, + damping: 20, + stiffness: 300, + useNativeDriver: true, + }), + ]).start(); + } + }, [isVisible, fadeAnim, scaleAnim]); + + const handleClose = React.useCallback(() => { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 0, + duration: 150, + useNativeDriver: true, + }), + Animated.timing(scaleAnim, { + toValue: 0.95, + duration: 150, + useNativeDriver: true, + }), + ]).start(() => { + setIsVisible(false); + setOpen(false); + }); + }, [fadeAnim, scaleAnim, setOpen]); + + if (!isVisible) return null; + + return ( + + + { + onInteractOutside?.(); + handleClose(); + }} + > + + + + + + {showCloseButton && ( + + + + )} + {children} + + + + + + + + + ); + } +); + +DialogContent.displayName = "DialogContent"; + +const DialogHeader = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogTitle.displayName = "DialogTitle"; + +const DialogDescription = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogDescription.displayName = "DialogDescription"; + +const DialogClose = React.forwardRef( + ({ children, ...props }, ref) => { + const { handleClose } = React.useContext(DialogContext); + + return React.cloneElement(children, { + ...children.props, + ...props, + onPress: (e: any) => { + children.props?.onPress?.(e); + handleClose?.(); + }, + }); + } +); + +DialogClose.displayName = "DialogClose"; + +export { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, + DialogClose, +}; +`} + previewCode={`import { Dialog } from "@nativeui/ui"; + +export default function DialogDemo() { + return ( +
+ Default Dialog + Delete + Outline + Secondary + Ghost + Link +
+ ); +}`} + registryName="dialog" + packageName="@nativeui/ui" + /> + ); +} diff --git a/app/(site)/docs/components/label/page.tsx b/app/(site)/docs/components/label/page.tsx new file mode 100644 index 0000000..0419f97 --- /dev/null +++ b/app/(site)/docs/components/label/page.tsx @@ -0,0 +1,105 @@ +import { ComponentPreview } from "@/components/docs/component-preview"; + +export default function LabelPage() { + return ( + \n Click me\n \n );\n}", + "language": "tsx" + } +]} + componentCode={`import * as React from "react"; +import { Text, Pressable } from "react-native"; +import { cn } from "@/lib/utils"; + +interface LabelProps { + children: React.ReactNode; + className?: string; + disabled?: boolean; + required?: boolean; + nativeID?: string; + onPress?: () => void; +} + +const Label = React.forwardRef( + ( + { className, disabled, required, children, nativeID, onPress, ...props }, + ref + ) => { + const content = ( + <> + {children} + {required && *} + + ); + + if (onPress) { + return ( + + + {content} + + + ); + } + + return ( + + {content} + + ); + } +); + +Label.displayName = "Label"; + +export { Label }; +`} + previewCode={`import { Label } from "@nativeui/ui"; + +export default function LabelDemo() { + return ( +
+ + + + + + +
+ ); +}`} + registryName="label" + packageName="@nativeui/ui" + /> + ); +} diff --git a/public/r/dialog.json b/public/r/dialog.json new file mode 100644 index 0000000..879a083 --- /dev/null +++ b/public/r/dialog.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "dialog", + "type": "registry:component", + "title": "Dialog", + "description": "A dialog component for React Native applications.", + "dependencies": [ + "react-native", + "@expo/vector-icons" + ], + "registryDependencies": [], + "files": [ + { + "path": "registry/dialog/dialog.tsx", + "content": "import * as React from \"react\";\nimport {\n View,\n Text,\n Pressable,\n Modal,\n TouchableWithoutFeedback,\n Platform,\n Animated,\n Dimensions,\n KeyboardAvoidingView,\n ScrollView,\n} from \"react-native\";\nimport { cn } from \"@/lib/utils\";\nimport { Ionicons } from \"@expo/vector-icons\";\n\ninterface DialogProps {\n children: React.ReactNode;\n className?: string;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\ninterface DialogTriggerProps {\n children: React.ReactNode;\n className?: string;\n disabled?: boolean;\n asChild?: boolean;\n}\n\ninterface DialogContentProps {\n children: React.ReactNode;\n className?: string;\n showCloseButton?: boolean;\n onInteractOutside?: () => void;\n}\n\ninterface DialogHeaderProps {\n className?: string;\n children: React.ReactNode;\n}\n\ninterface DialogFooterProps {\n className?: string;\n children: React.ReactNode;\n}\n\ninterface DialogTitleProps {\n className?: string;\n children: React.ReactNode;\n}\n\ninterface DialogDescriptionProps {\n className?: string;\n children: React.ReactNode;\n}\n\ninterface DialogCloseProps {\n children: React.ReactElement<{ onPress?: (e: any) => void }>;\n className?: string;\n}\n\nconst DialogContext = React.createContext<{\n open: boolean;\n setOpen: (open: boolean) => void;\n handleClose?: () => void;\n}>({\n open: false,\n setOpen: () => {},\n});\n\nconst Dialog = React.forwardRef(\n ({ children, className, open = false, onOpenChange, ...props }, ref) => {\n const [internalOpen, setInternalOpen] = React.useState(false);\n\n const isControlled = open !== undefined;\n const isOpen = isControlled ? open : internalOpen;\n\n const setOpen = React.useCallback(\n (value: boolean) => {\n if (!isControlled) {\n setInternalOpen(value);\n }\n onOpenChange?.(value);\n },\n [isControlled, onOpenChange]\n );\n\n return (\n \n \n {children}\n \n \n );\n }\n);\n\nDialog.displayName = \"Dialog\";\n\nconst DialogTrigger = React.forwardRef(\n (\n { children, className, disabled = false, asChild = false, ...props },\n ref\n ) => {\n const { setOpen } = React.useContext(DialogContext);\n\n if (asChild) {\n const child = React.Children.only(children) as React.ReactElement<{\n onPress?: (e: any) => void;\n ref?: React.Ref;\n disabled?: boolean;\n }>;\n return React.cloneElement(child, {\n ...props,\n ref,\n onPress: (e: any) => {\n child.props?.onPress?.(e);\n setOpen(true);\n },\n disabled,\n });\n }\n\n return (\n setOpen(true)}\n accessibilityRole=\"button\"\n {...props}\n >\n {children}\n \n );\n }\n);\n\nDialogTrigger.displayName = \"DialogTrigger\";\n\nconst DialogContent = React.forwardRef(\n (\n {\n children,\n className,\n showCloseButton = true,\n onInteractOutside,\n ...props\n },\n ref\n ) => {\n const { open, setOpen } = React.useContext(DialogContext);\n const fadeAnim = React.useRef(new Animated.Value(0)).current;\n const scaleAnim = React.useRef(new Animated.Value(0.95)).current;\n const { height: SCREEN_HEIGHT } = Dimensions.get(\"window\");\n const [isVisible, setIsVisible] = React.useState(open);\n\n React.useEffect(() => {\n if (open && !isVisible) {\n setIsVisible(true);\n }\n }, [open, isVisible]);\n\n React.useEffect(() => {\n if (isVisible) {\n Animated.parallel([\n Animated.timing(fadeAnim, {\n toValue: 1,\n duration: 200,\n useNativeDriver: true,\n }),\n Animated.spring(scaleAnim, {\n toValue: 1,\n damping: 20,\n stiffness: 300,\n useNativeDriver: true,\n }),\n ]).start();\n }\n }, [isVisible, fadeAnim, scaleAnim]);\n\n const handleClose = React.useCallback(() => {\n Animated.parallel([\n Animated.timing(fadeAnim, {\n toValue: 0,\n duration: 150,\n useNativeDriver: true,\n }),\n Animated.timing(scaleAnim, {\n toValue: 0.95,\n duration: 150,\n useNativeDriver: true,\n }),\n ]).start(() => {\n setIsVisible(false);\n setOpen(false);\n });\n }, [fadeAnim, scaleAnim, setOpen]);\n\n if (!isVisible) return null;\n\n return (\n \n \n {\n onInteractOutside?.();\n handleClose();\n }}\n >\n \n \n \n \n \n {showCloseButton && (\n \n \n \n )}\n {children}\n \n \n \n \n \n \n \n \n );\n }\n);\n\nDialogContent.displayName = \"DialogContent\";\n\nconst DialogHeader = React.forwardRef(\n ({ className, children, ...props }, ref) => (\n \n {children}\n \n )\n);\n\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogFooter = React.forwardRef(\n ({ className, children, ...props }, ref) => (\n \n {children}\n \n )\n);\n\nDialogFooter.displayName = \"DialogFooter\";\n\nconst DialogTitle = React.forwardRef(\n ({ className, children, ...props }, ref) => (\n \n {children}\n \n )\n);\n\nDialogTitle.displayName = \"DialogTitle\";\n\nconst DialogDescription = React.forwardRef(\n ({ className, children, ...props }, ref) => (\n \n {children}\n \n )\n);\n\nDialogDescription.displayName = \"DialogDescription\";\n\nconst DialogClose = React.forwardRef(\n ({ children, ...props }, ref) => {\n const { handleClose } = React.useContext(DialogContext);\n\n return React.cloneElement(children, {\n ...children.props,\n ...props,\n onPress: (e: any) => {\n children.props?.onPress?.(e);\n handleClose?.();\n },\n });\n }\n);\n\nDialogClose.displayName = \"DialogClose\";\n\nexport {\n Dialog,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n DialogClose,\n};\n", + "type": "registry:component" + } + ] +} \ No newline at end of file diff --git a/public/r/label.json b/public/r/label.json new file mode 100644 index 0000000..591dfa3 --- /dev/null +++ b/public/r/label.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "label", + "type": "registry:component", + "title": "Label", + "description": "A label component for React Native applications.", + "dependencies": [ + "react-native" + ], + "registryDependencies": [], + "files": [ + { + "path": "registry/label/label.tsx", + "content": "import * as React from \"react\";\nimport { Text, Pressable } from \"react-native\";\nimport { cn } from \"@/lib/utils\";\n\ninterface LabelProps {\n children: React.ReactNode;\n className?: string;\n disabled?: boolean;\n required?: boolean;\n nativeID?: string;\n onPress?: () => void;\n}\n\nconst Label = React.forwardRef(\n (\n { className, disabled, required, children, nativeID, onPress, ...props },\n ref\n ) => {\n const content = (\n <>\n {children}\n {required && *}\n \n );\n\n if (onPress) {\n return (\n \n \n {content}\n \n \n );\n }\n\n return (\n \n {content}\n \n );\n }\n);\n\nLabel.displayName = \"Label\";\n\nexport { Label };\n", + "type": "registry:component" + } + ] +} \ No newline at end of file diff --git a/registry.json b/registry.json index d2f918f..ced91a3 100644 --- a/registry.json +++ b/registry.json @@ -413,6 +413,20 @@ ], "dependencies": ["react-native"], "registryDependencies": [] + }, + { + "name": "dialog", + "type": "registry:component", + "title": "Dialog", + "description": "A dialog component for React Native applications.", + "files": [ + { + "path": "registry/dialog/dialog.tsx", + "type": "registry:component" + } + ], + "dependencies": ["react-native", "@expo/vector-icons"], + "registryDependencies": [] } ] } diff --git a/registry/dialog/dialog.tsx b/registry/dialog/dialog.tsx new file mode 100644 index 0000000..ea9f981 --- /dev/null +++ b/registry/dialog/dialog.tsx @@ -0,0 +1,352 @@ +import * as React from "react"; +import { + View, + Text, + Pressable, + Modal, + TouchableWithoutFeedback, + Platform, + Animated, + Dimensions, + KeyboardAvoidingView, + ScrollView, +} from "react-native"; +import { cn } from "@/lib/utils"; +import { Ionicons } from "@expo/vector-icons"; + +interface DialogProps { + children: React.ReactNode; + className?: string; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +interface DialogTriggerProps { + children: React.ReactNode; + className?: string; + disabled?: boolean; + asChild?: boolean; +} + +interface DialogContentProps { + children: React.ReactNode; + className?: string; + showCloseButton?: boolean; + onInteractOutside?: () => void; +} + +interface DialogHeaderProps { + className?: string; + children: React.ReactNode; +} + +interface DialogFooterProps { + className?: string; + children: React.ReactNode; +} + +interface DialogTitleProps { + className?: string; + children: React.ReactNode; +} + +interface DialogDescriptionProps { + className?: string; + children: React.ReactNode; +} + +interface DialogCloseProps { + children: React.ReactElement<{ onPress?: (e: any) => void }>; + className?: string; +} + +const DialogContext = React.createContext<{ + open: boolean; + setOpen: (open: boolean) => void; + handleClose?: () => void; +}>({ + open: false, + setOpen: () => {}, +}); + +const Dialog = React.forwardRef( + ({ children, className, open = false, onOpenChange, ...props }, ref) => { + const [internalOpen, setInternalOpen] = React.useState(false); + + const isControlled = open !== undefined; + const isOpen = isControlled ? open : internalOpen; + + const setOpen = React.useCallback( + (value: boolean) => { + if (!isControlled) { + setInternalOpen(value); + } + onOpenChange?.(value); + }, + [isControlled, onOpenChange] + ); + + return ( + + + {children} + + + ); + } +); + +Dialog.displayName = "Dialog"; + +const DialogTrigger = React.forwardRef( + ( + { children, className, disabled = false, asChild = false, ...props }, + ref + ) => { + const { setOpen } = React.useContext(DialogContext); + + if (asChild) { + const child = React.Children.only(children) as React.ReactElement<{ + onPress?: (e: any) => void; + ref?: React.Ref; + disabled?: boolean; + }>; + return React.cloneElement(child, { + ...props, + ref, + onPress: (e: any) => { + child.props?.onPress?.(e); + setOpen(true); + }, + disabled, + }); + } + + return ( + setOpen(true)} + accessibilityRole="button" + {...props} + > + {children} + + ); + } +); + +DialogTrigger.displayName = "DialogTrigger"; + +const DialogContent = React.forwardRef( + ( + { + children, + className, + showCloseButton = true, + onInteractOutside, + ...props + }, + ref + ) => { + const { open, setOpen } = React.useContext(DialogContext); + const fadeAnim = React.useRef(new Animated.Value(0)).current; + const scaleAnim = React.useRef(new Animated.Value(0.95)).current; + const { height: SCREEN_HEIGHT } = Dimensions.get("window"); + const [isVisible, setIsVisible] = React.useState(open); + + React.useEffect(() => { + if (open && !isVisible) { + setIsVisible(true); + } + }, [open, isVisible]); + + React.useEffect(() => { + if (isVisible) { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + Animated.spring(scaleAnim, { + toValue: 1, + damping: 20, + stiffness: 300, + useNativeDriver: true, + }), + ]).start(); + } + }, [isVisible, fadeAnim, scaleAnim]); + + const handleClose = React.useCallback(() => { + Animated.parallel([ + Animated.timing(fadeAnim, { + toValue: 0, + duration: 150, + useNativeDriver: true, + }), + Animated.timing(scaleAnim, { + toValue: 0.95, + duration: 150, + useNativeDriver: true, + }), + ]).start(() => { + setIsVisible(false); + setOpen(false); + }); + }, [fadeAnim, scaleAnim, setOpen]); + + if (!isVisible) return null; + + return ( + + + { + onInteractOutside?.(); + handleClose(); + }} + > + + + + + + {showCloseButton && ( + + + + )} + {children} + + + + + + + + + ); + } +); + +DialogContent.displayName = "DialogContent"; + +const DialogHeader = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogTitle.displayName = "DialogTitle"; + +const DialogDescription = React.forwardRef( + ({ className, children, ...props }, ref) => ( + + {children} + + ) +); + +DialogDescription.displayName = "DialogDescription"; + +const DialogClose = React.forwardRef( + ({ children, ...props }, ref) => { + const { handleClose } = React.useContext(DialogContext); + + return React.cloneElement(children, { + ...children.props, + ...props, + onPress: (e: any) => { + children.props?.onPress?.(e); + handleClose?.(); + }, + }); + } +); + +DialogClose.displayName = "DialogClose"; + +export { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, + DialogClose, +};