From bc307be73f929bb1e335a4721124942be5bda2ee Mon Sep 17 00:00:00 2001 From: ndevasan Date: Fri, 7 Mar 2025 18:39:15 +0530 Subject: [PATCH 01/18] Add components --- .gitignore | 2 +- components.json | 6 + package.json | 37 +- src/components/NAccordion.tsx | 28 + src/components/NAlert.tsx | 21 + src/components/NButton.tsx | 22 + src/components/NButtonGroup.tsx | 38 + src/components/NCard.tsx | 6 + src/components/NCheck.tsx | 20 + src/components/NColorPicker.tsx | 46 + src/components/NConfirm.tsx | 42 + src/components/NDatePicker.tsx | 69 + src/components/NDialog.tsx | 26 + src/components/NDivider.tsx | 11 + src/components/NInput.tsx | 31 + src/components/NKeyboardScrollView.tsx | 37 + src/components/NLinkify.tsx | 138 ++ src/components/NLoading.tsx | 11 + src/components/NMenuItem.tsx | 31 + src/components/NPopover.tsx | 29 + src/components/NPress.tsx | 12 + src/components/NProgress.tsx | 14 + src/components/NRadio.tsx | 52 + src/components/NRequired.tsx | 6 + src/components/NSelect.tsx | 49 + src/components/NSheet.tsx | 35 + src/components/NSkeleton.tsx | 9 + src/components/NSwitch.tsx | 31 + src/components/NText.tsx | 17 +- src/components/NTextarea.tsx | 33 + src/components/NTheme.tsx | 55 + src/components/NToast.tsx | 65 + src/components/NTooltip.tsx | 31 + src/components/ui/accordion.tsx | 103 ++ src/components/ui/alert-dialog.tsx | 125 ++ src/components/ui/alert.tsx | 52 + src/components/ui/button.tsx | 73 + src/components/ui/checkbox.tsx | 25 + src/components/ui/dialog.tsx | 107 ++ src/components/ui/input.tsx | 22 + src/components/ui/label.tsx | 21 + src/components/ui/popover.tsx | 38 + src/components/ui/progress.tsx | 47 + src/components/ui/radio-group.tsx | 29 + src/components/ui/select.tsx | 143 ++ src/components/ui/separator.tsx | 18 + src/components/ui/skeleton.tsx | 21 + src/components/ui/switch.tsx | 75 + src/components/ui/text.tsx | 16 + src/components/ui/textarea.tsx | 27 + src/components/ui/tooltip.tsx | 36 + src/hooks/useColorScheme.tsx | 30 + src/lib/android-navigation-bar.ts | 9 + src/lib/icons/AlertCircle.tsx | 5 + src/lib/icons/Calender.tsx | 5 + src/lib/icons/Check.tsx | 5 + src/lib/icons/ChevronDown.tsx | 5 + src/lib/icons/ChevronUp.tsx | 5 + src/lib/icons/MoonStar.tsx | 5 + src/lib/icons/Sun.tsx | 5 + src/lib/icons/X.tsx | 5 + src/lib/icons/iconWithClassName.ts | 14 + src/lib/utils.ts | 6 + src/services/StorageService.ts | 22 + yarn.lock | 1810 +++++++++++++++++++++--- 65 files changed, 3785 insertions(+), 184 deletions(-) create mode 100644 components.json create mode 100644 src/components/NAccordion.tsx create mode 100644 src/components/NAlert.tsx create mode 100644 src/components/NButton.tsx create mode 100644 src/components/NButtonGroup.tsx create mode 100644 src/components/NCard.tsx create mode 100644 src/components/NCheck.tsx create mode 100644 src/components/NColorPicker.tsx create mode 100644 src/components/NConfirm.tsx create mode 100644 src/components/NDatePicker.tsx create mode 100644 src/components/NDialog.tsx create mode 100644 src/components/NDivider.tsx create mode 100644 src/components/NInput.tsx create mode 100644 src/components/NKeyboardScrollView.tsx create mode 100644 src/components/NLinkify.tsx create mode 100644 src/components/NLoading.tsx create mode 100644 src/components/NMenuItem.tsx create mode 100644 src/components/NPopover.tsx create mode 100644 src/components/NPress.tsx create mode 100644 src/components/NProgress.tsx create mode 100644 src/components/NRadio.tsx create mode 100644 src/components/NRequired.tsx create mode 100644 src/components/NSelect.tsx create mode 100644 src/components/NSheet.tsx create mode 100644 src/components/NSkeleton.tsx create mode 100644 src/components/NSwitch.tsx create mode 100644 src/components/NTextarea.tsx create mode 100644 src/components/NTheme.tsx create mode 100644 src/components/NToast.tsx create mode 100644 src/components/NTooltip.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/text.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/hooks/useColorScheme.tsx create mode 100644 src/lib/android-navigation-bar.ts create mode 100644 src/lib/icons/AlertCircle.tsx create mode 100644 src/lib/icons/Calender.tsx create mode 100644 src/lib/icons/Check.tsx create mode 100644 src/lib/icons/ChevronDown.tsx create mode 100644 src/lib/icons/ChevronUp.tsx create mode 100644 src/lib/icons/MoonStar.tsx create mode 100644 src/lib/icons/Sun.tsx create mode 100644 src/lib/icons/X.tsx create mode 100644 src/lib/icons/iconWithClassName.ts create mode 100644 src/lib/utils.ts create mode 100644 src/services/StorageService.ts diff --git a/.gitignore b/.gitignore index 67f3212..f1be469 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,7 @@ android/keystores/debug.keystore .turbo/ # generated by bob -lib/ +dist/ # React Native Codegen ios/generated diff --git a/components.json b/components.json new file mode 100644 index 0000000..044a956 --- /dev/null +++ b/components.json @@ -0,0 +1,6 @@ +{ + "aliases": { + "components": "@/components", + "lib": "@/lib" + } +} diff --git a/package.json b/package.json index 43e06c8..6899a59 100644 --- a/package.json +++ b/package.json @@ -88,9 +88,13 @@ "@types/react": "^18.2.44" }, "peerDependencies": { + "@expo/vector-icons": "*", + "@react-navigation/core": ">=6", + "@react-navigation/native": ">=6", "nativewind": ">=4", "react": "*", "react-native": "*", + "react-native-gesture-handler": ">=3", "react-native-reanimated": ">=3", "tailwindcss": ">=3" }, @@ -160,7 +164,7 @@ }, "react-native-builder-bob": { "source": "src", - "output": "lib", + "output": "dist", "targets": [ [ "commonjs", @@ -189,5 +193,36 @@ "languages": "js", "type": "library", "version": "0.48.2" + }, + "dependencies": { + "@gorhom/bottom-sheet": "^5.1.1", + "@rn-primitives/accordion": "^1.1.0", + "@rn-primitives/alert-dialog": "^1.1.0", + "@rn-primitives/checkbox": "^1.1.0", + "@rn-primitives/dialog": "^1.1.0", + "@rn-primitives/label": "^1.1.0", + "@rn-primitives/popover": "^1.1.0", + "@rn-primitives/portal": "^1.1.0", + "@rn-primitives/progress": "^1.1.0", + "@rn-primitives/radio-group": "^1.1.0", + "@rn-primitives/select": "^1.1.0", + "@rn-primitives/separator": "^1.1.0", + "@rn-primitives/slot": "^1.1.0", + "@rn-primitives/switch": "^1.1.0", + "@rn-primitives/tooltip": "^1.1.0", + "@rn-primitives/types": "^1.1.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "expo-navigation-bar": "^4.0.8", + "expo-status-bar": "^2.0.1", + "lucide-react-native": "^0.479.0", + "react-native-keyboard-controller": "^1.16.7", + "react-native-mmkv": "^3.2.0", + "react-native-modal-datetime-picker": "^18.0.0", + "react-native-safe-area-context": "^5.3.0", + "react-native-toast-message": "^2.2.1", + "react-native-wheel-color-picker": "^1.3.1", + "tailwind-merge": "^3.0.2" } } diff --git a/src/components/NAccordion.tsx b/src/components/NAccordion.tsx new file mode 100644 index 0000000..f890f3f --- /dev/null +++ b/src/components/NAccordion.tsx @@ -0,0 +1,28 @@ +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface Props { + className?: string; + titleClassName?: string; + descriptionClassName?: string; + items: { title: string; description: string }[]; +} + +export const NAccordion = (props: Props) => { + const { items, className = '', titleClassName = '', descriptionClassName = '' } = props; + return ( + + {items.map((item: any, index) => ( + + + {item.title} + + + {item.description} + + + ))} + + ); +}; diff --git a/src/components/NAlert.tsx b/src/components/NAlert.tsx new file mode 100644 index 0000000..5e7da45 --- /dev/null +++ b/src/components/NAlert.tsx @@ -0,0 +1,21 @@ +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { AlertCircle } from '@/lib/icons/AlertCircle'; +import { cn } from '@/lib/utils'; + +interface Props { + title?: string; + description: string; + className?: string; + titleClassName?: string; + descriptionClassName?: string; +} + +export const NAlert = (props: Props) => { + const { title = '', description, className = '', titleClassName = '', descriptionClassName = '' } = props; + return ( + + {title && {title}} + {description} + + ); +}; diff --git a/src/components/NButton.tsx b/src/components/NButton.tsx new file mode 100644 index 0000000..d7942e8 --- /dev/null +++ b/src/components/NButton.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import { PressableProps } from 'react-native'; +import { NPress } from '@/components/NPress'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface Props extends PressableProps { + className?: string; + textClassName?: string; +} + +export const NButton = (props: Props) => { + const { className = '', textClassName = '', ...remaining } = props; + return ( + + {props.children as any} + + ); +}; diff --git a/src/components/NButtonGroup.tsx b/src/components/NButtonGroup.tsx new file mode 100644 index 0000000..717c792 --- /dev/null +++ b/src/components/NButtonGroup.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { View } from 'react-native'; +import { Button } from '@/components/ui/button'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface Props { + className?: string; + label: string; + size?: 'sm' | 'default' | 'lg' | any; + items: string[]; + value: string; + disabled?: boolean; + onChange: (selected: string) => void; +} + +export const NButtonGroup = (props: Props) => { + const { className = '', items, value, size = 'default', label, disabled = false, onChange } = props; + return ( + + {label && {label}} + + {items.map((item, index) => ( + + ))} + + + ); +}; diff --git a/src/components/NCard.tsx b/src/components/NCard.tsx new file mode 100644 index 0000000..7221af9 --- /dev/null +++ b/src/components/NCard.tsx @@ -0,0 +1,6 @@ +import { View, ViewProps } from 'react-native'; +import { cn } from '@/lib/utils'; + +export const NCard = (props: ViewProps) => { + return {props.children}; +}; diff --git a/src/components/NCheck.tsx b/src/components/NCheck.tsx new file mode 100644 index 0000000..da6956b --- /dev/null +++ b/src/components/NCheck.tsx @@ -0,0 +1,20 @@ +import { View } from 'react-native'; +import { Checkbox } from '@/components/ui/checkbox'; +import { NText } from '@/components/NText'; + +interface Props { + checked: boolean; + disabled?: boolean; + label: string; + onChange: (checked: boolean) => void; +} + +export const NCheck = (props: Props) => { + const { checked = false, disabled = false, label = '', onChange } = props; + return ( + + + {label && {label}} + + ); +}; diff --git a/src/components/NColorPicker.tsx b/src/components/NColorPicker.tsx new file mode 100644 index 0000000..7c28401 --- /dev/null +++ b/src/components/NColorPicker.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Pressable, View } from 'react-native'; +import ColorPicker from 'react-native-wheel-color-picker'; +import { NPopover } from '@/components/NPopover'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface Props { + value: string; + label?: string; + disabled?: boolean; + onChange: (value: string) => void; +} + +export const NColorPicker = (props: Props) => { + const { label = '', value = '#ffffff', disabled = false, onChange } = props; + + return ( + + {label && {label}} + + }> + + + + + + ); +}; diff --git a/src/components/NConfirm.tsx b/src/components/NConfirm.tsx new file mode 100644 index 0000000..fb38b85 --- /dev/null +++ b/src/components/NConfirm.tsx @@ -0,0 +1,42 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger +} from '@/components/ui/alert-dialog'; +import { NText } from '@/components/NText'; + +interface Props { + title: string; + description: string; + onResult: (result: boolean) => void; + children: React.ReactNode; +} + +export const NConfirm = (props: Props) => { + const { title, description, children, onResult } = props; + return ( + + {children} + + + {title} + {description} + + + onResult(true)}> + Continue + + onResult(false)}> + Cancel + + + + + ); +}; diff --git a/src/components/NDatePicker.tsx b/src/components/NDatePicker.tsx new file mode 100644 index 0000000..1bc45d5 --- /dev/null +++ b/src/components/NDatePicker.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react'; +import { View } from 'react-native'; +import { format } from 'date-fns'; +import DateTimePickerModal from 'react-native-modal-datetime-picker'; +import { NText } from '@/components/NText'; +import { Calendar } from '@/lib/icons/Calender'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { cn } from '@/lib/utils'; +import { NPress } from '@/components/NPress'; + +interface Props { + value: Date; + label?: string; + isDarkMode?: boolean; + disabled?: boolean; + className?: string; + type?: 'date' | 'time' | 'datetime'; + onChange: (date: Date) => void; +} + +export const NDatePicker = (props: Props) => { + const { isDarkColorScheme } = useColorScheme(); + const { label = '', type = 'date', isDarkMode = isDarkColorScheme, disabled = false, className = '', value, onChange } = props; + const [isVisible, setIsVisible] = useState(false); + + const hideDatePicker = () => { + setIsVisible(false); + }; + + const showDatePicker = () => { + setIsVisible(true); + }; + + const handleConfirm = (date: Date) => { + onChange(date); + hideDatePicker(); + }; + + const formatValue = (date: Date) => { + if (type === 'time') { + return format(date, 'HH:mm:ss'); + } + if (type === 'date') { + return format(date, 'yyyy-MM-dd'); + } + return format(date, 'yyy-MM-dd HH:mm:ss'); + }; + + return ( + + {label && {label}} + + + {formatValue(value)} + + + + ); +}; diff --git a/src/components/NDialog.tsx b/src/components/NDialog.tsx new file mode 100644 index 0000000..696d405 --- /dev/null +++ b/src/components/NDialog.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { View } from 'react-native'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; + +interface Props { + children?: React.ReactNode; + trigger?: React.ReactNode; + title: string; + buttonText?: string; + className?: string; +} + +export const NDialog = (props: Props) => { + const { children, trigger, title, className = '' } = props; + return ( + + {trigger} + + + {title} + + {children} + + + ); +}; diff --git a/src/components/NDivider.tsx b/src/components/NDivider.tsx new file mode 100644 index 0000000..610b27a --- /dev/null +++ b/src/components/NDivider.tsx @@ -0,0 +1,11 @@ +import { Separator } from '@/components/ui/separator'; + +interface Props { + className?: string; + orientation?: 'horizontal' | 'vertical'; +} + +export const NDivider = (props: Props) => { + const { orientation = 'horizontal', className } = props; + return ; +}; diff --git a/src/components/NInput.tsx b/src/components/NInput.tsx new file mode 100644 index 0000000..dc57570 --- /dev/null +++ b/src/components/NInput.tsx @@ -0,0 +1,31 @@ +import { type TextInputProps, View } from 'react-native'; +import { Input } from '@/components/ui/input'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface Props extends TextInputProps { + value: string; + label?: string; + placeholder?: string; + disabled?: boolean; + className?: string; + onChangeText: (text: string) => void; +} + +export const NInput = (props: Props) => { + const { value, label = '', placeholder = '', className = '', onChangeText, disabled = false, ...remaining } = props; + + return ( + + {label && {label}} + + + ); +}; diff --git a/src/components/NKeyboardScrollView.tsx b/src/components/NKeyboardScrollView.tsx new file mode 100644 index 0000000..897c992 --- /dev/null +++ b/src/components/NKeyboardScrollView.tsx @@ -0,0 +1,37 @@ +import { StyleProp, ViewStyle } from 'react-native'; +import { DefaultKeyboardToolbarTheme, KeyboardAwareScrollView, KeyboardToolbar, KeyboardToolbarProps } from 'react-native-keyboard-controller'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +interface Props { + children: any; + className?: string; + style?: StyleProp; + contentStyle?: StyleProp; +} + +export const NKeyboardScrollView = (props: Props) => { + const { colors } = useColorScheme(); + const { children, style = {}, contentStyle = {}, className = '' } = props; + + const theme: KeyboardToolbarProps['theme'] = { + ...DefaultKeyboardToolbarTheme, + dark: { + ...DefaultKeyboardToolbarTheme.dark, + primary: colors.primary + } + }; + + return ( + <> + + {children} + + + + ); +}; diff --git a/src/components/NLinkify.tsx b/src/components/NLinkify.tsx new file mode 100644 index 0000000..5d055a8 --- /dev/null +++ b/src/components/NLinkify.tsx @@ -0,0 +1,138 @@ +import React, { ReactNode, useEffect, useState } from 'react'; +import { Linking, Platform, Text, TextProps, TextStyle, View, ViewProps } from 'react-native'; +// @ts-ignore +import linkifyIt from 'linkify-it'; +// @ts-ignore +import mdurl from 'mdurl'; +import { NText } from '@/components/NText'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +const defaultLinkify = linkifyIt(); +const { OS } = Platform; + +type LinkifyProps = ViewProps & { + children: ReactNode; + style?: TextStyle; + linkify?: any; + linkStyle?: TextStyle; + linkText?: string | ((url: string) => string); + onPress?: (url: string, text: string) => void; + onLongPress?: (url: string, text: string) => void; + injectViewProps?: (url: string) => object; +}; + +const Linkify = (props: LinkifyProps) => { + const { colors } = useColorScheme(); + const { + children, + style, + linkify: customLinkify, + linkStyle = { color: colors.primary }, + linkText, + onPress, + onLongPress, + injectViewProps = () => ({}), + ...viewProps + } = props; + + const [linkifyInstance, setLinkifyInstance] = useState(() => customLinkify || defaultLinkify); + + useEffect(() => { + if (customLinkify && customLinkify !== linkifyInstance) { + setLinkifyInstance(customLinkify); + } + }, [customLinkify]); + + if (!linkifyInstance) { + console.error('Linkify instance is undefined.'); + return ( + + {children} + + ); + } + + const isTextNested = (component: React.ReactElement) => { + if (!React.isValidElement(component)) throw new Error('Invalid component'); + // @ts-ignore + return typeof component.props.children !== 'string'; + }; + + const processLinkify = (component: React.ReactElement) => { + const textContent = component.props.children as string; + if (!textContent || typeof textContent !== 'string') return component; + + if (!linkifyInstance.pretest(textContent) || !linkifyInstance.test(textContent)) { + return component; + } + + let elements: ReactNode[] = []; + let lastIndex = 0; + + try { + linkifyInstance + .tlds('app', true) + .match(textContent) + ?.forEach(({ index, lastIndex: endIndex, text, url }: any) => { + const nonLinkedText = textContent.substring(lastIndex, index); + if (nonLinkedText) elements.push(nonLinkedText); + lastIndex = endIndex; + + const displayText = typeof linkText === 'function' ? linkText(url) : linkText || text; + const clickHandlers = { + onPress: onPress ? () => onPress(url, displayText) : undefined, + onLongPress: OS !== 'web' && onLongPress ? () => onLongPress(url, displayText) : undefined + }; + + elements.push( + + {displayText} + + ); + }); + + elements.push(textContent.substring(lastIndex)); + return React.cloneElement(component, {}, elements); + } catch (err) { + console.error('Error in processLinkify:', err); + return component; + } + }; + + const parseComponent = (component: React.ReactElement): React.ReactElement => { + if (!component?.props?.children) return component; + return React.cloneElement( + component, + {}, + React.Children.map(component.props.children, child => { + if (typeof child === 'string' && linkifyInstance?.pretest?.(child)) { + return processLinkify({child}); + } + if (React.isValidElement(child) && child.type === Text && !isTextNested(child)) { + return processLinkify(child as any); + } + return parseComponent(child as React.ReactElement); + }) + ); + }; + + return ( + + {!onPress && !onLongPress && !linkStyle ? children : parseComponent({children}).props.children} + + ); +}; + +type NLinkifyProps = LinkifyProps & { linkDefault?: boolean }; + +export const NLinkify: React.FC = ({ linkDefault, ...props }) => { + const handleLink = (url: string) => { + const urlObject = mdurl.parse(url); + urlObject.protocol = urlObject.protocol.toLowerCase(); + const normalizedURL = mdurl.format(urlObject); + + Linking.canOpenURL(normalizedURL).then(supported => supported && Linking.openURL(normalizedURL)); + }; + + return ; +}; diff --git a/src/components/NLoading.tsx b/src/components/NLoading.tsx new file mode 100644 index 0000000..0f9b0be --- /dev/null +++ b/src/components/NLoading.tsx @@ -0,0 +1,11 @@ +import { ActivityIndicator, View } from 'react-native'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +export const NLoading = () => { + const { colors } = useColorScheme(); + return ( + + + + ); +}; diff --git a/src/components/NMenuItem.tsx b/src/components/NMenuItem.tsx new file mode 100644 index 0000000..61aa865 --- /dev/null +++ b/src/components/NMenuItem.tsx @@ -0,0 +1,31 @@ +import { View } from 'react-native'; +import { NPress } from '@/components/NPress'; +import { NText } from '@/components/NText'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { cn } from '@/lib/utils'; +import Ionicons from '@expo/vector-icons/Ionicons'; + +interface Props { + name: string; + description?: string; + className?: string; + icon: any; + onPress: () => void; +} + +const NMenuItem = (props: Props) => { + const { name, description, className = '', icon, onPress } = props; + const { colors } = useColorScheme(); + + return ( + + + + {name} + {description && {description}} + + + ); +}; + +export default NMenuItem; diff --git a/src/components/NPopover.tsx b/src/components/NPopover.tsx new file mode 100644 index 0000000..77b3934 --- /dev/null +++ b/src/components/NPopover.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { Platform } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; + +interface Props { + trigger?: React.ReactNode; + children: React.ReactNode; +} + +export const NPopover = (props: Props) => { + const { trigger, children } = props; + const insets = useSafeAreaInsets(); + const contentInsets = { + top: insets.top, + bottom: insets.bottom, + left: 12, + right: 12 + }; + + return ( + + {trigger} + + {children} + + + ); +}; diff --git a/src/components/NPress.tsx b/src/components/NPress.tsx new file mode 100644 index 0000000..b97f726 --- /dev/null +++ b/src/components/NPress.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Pressable, PressableProps } from 'react-native'; +import { cn } from '@/lib/utils'; + +export const NPress = (props: PressableProps) => { + const { children, className = '', ...remaining } = props; + return ( + + {children} + + ); +}; diff --git a/src/components/NProgress.tsx b/src/components/NProgress.tsx new file mode 100644 index 0000000..bc85543 --- /dev/null +++ b/src/components/NProgress.tsx @@ -0,0 +1,14 @@ +import { Progress } from '@/components/ui/progress'; + +interface Props { + className?: string; + indicatorClassName?: string; + value: number; +} + +export const NProgress = (props: Props) => { + const { value, className = '', indicatorClassName = '' } = props; + return ( + + ); +}; diff --git a/src/components/NRadio.tsx b/src/components/NRadio.tsx new file mode 100644 index 0000000..0cb56b0 --- /dev/null +++ b/src/components/NRadio.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import { View } from 'react-native'; +import { Label } from '@/components/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface RadioGroupProps { + disabled?: boolean; + item: { label: string; value: string }; + onLabelPress: (value: string) => void; +} + +const RadioGroupItemWithLabel = (props: RadioGroupProps) => { + const { disabled, item, onLabelPress } = props; + + return ( + + + + + ); +}; + +interface Props { + label?: string; + disabled?: boolean; + value: string; + items: { label: string; value: string }[]; + onChange: (value: string) => void; +} + +export const NRadio = (props: Props) => { + const { label, value, items, disabled = false, onChange } = props; + + return ( + + {label} + + {items.map(item => ( + + ))} + + + ); +}; diff --git a/src/components/NRequired.tsx b/src/components/NRequired.tsx new file mode 100644 index 0000000..94e746a --- /dev/null +++ b/src/components/NRequired.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { NText } from '@/components/NText'; + +export const NRequired = () => { + return *; +}; diff --git a/src/components/NSelect.tsx b/src/components/NSelect.tsx new file mode 100644 index 0000000..40ed3a7 --- /dev/null +++ b/src/components/NSelect.tsx @@ -0,0 +1,49 @@ +import { ScrollView, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface Props { + label?: string; + selectLabel?: string; + placeholder?: string; + disabled?: boolean; + value: { label: string; value: string }; + items: { label: string; value: string }[]; + onChange: (value: any) => void; +} + +export const NSelect = (props: Props) => { + const { label = '', selectLabel = '', placeholder = '', disabled = false, value, items, onChange } = props; + const insets = useSafeAreaInsets(); + const contentInsets = { + top: insets.top, + bottom: insets.bottom, + left: 12, + right: 12 + }; + + return ( + + {label && {label}} + + + ); +}; diff --git a/src/components/NSheet.tsx b/src/components/NSheet.tsx new file mode 100644 index 0000000..2b2ecb1 --- /dev/null +++ b/src/components/NSheet.tsx @@ -0,0 +1,35 @@ +import { useCallback } from 'react'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet'; + +interface Props { + sheetRef: any; + snapPoints?: string[] | number[]; + children: React.ReactNode; +} + +export const NSheet = (props: Props) => { + const { colors } = useColorScheme(); + const { sheetRef, children, snapPoints = null } = props; + + const renderBackdrop = useCallback( + (props: BottomSheetBackdropProps) => ( + + ), + [] + ); + + return ( + + {children} + + ); +}; diff --git a/src/components/NSkeleton.tsx b/src/components/NSkeleton.tsx new file mode 100644 index 0000000..fc428f2 --- /dev/null +++ b/src/components/NSkeleton.tsx @@ -0,0 +1,9 @@ +import { Skeleton } from '@/components/ui/skeleton'; + +interface Props { + className?: string; +} + +export const NSkeleton = (props: Props) => { + return ; +}; diff --git a/src/components/NSwitch.tsx b/src/components/NSwitch.tsx new file mode 100644 index 0000000..12514ad --- /dev/null +++ b/src/components/NSwitch.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { View } from 'react-native'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; + +interface Props { + label?: string; + checked: boolean; + className?: string; + disabled?: boolean; + onChange: (checked: boolean) => void; +} + +export const NSwitch = (props: Props) => { + const { label, className = '', disabled = false, onChange, checked = false } = props; + + return ( + + + + + ); +}; diff --git a/src/components/NText.tsx b/src/components/NText.tsx index a5c17dc..c91b4e2 100644 --- a/src/components/NText.tsx +++ b/src/components/NText.tsx @@ -1,5 +1,16 @@ -import { Text } from 'react-native'; +import { Text, TextProps } from 'react-native'; +import { cn } from '@/lib/utils'; -export const NText = (props: any) => { - return Sample; +interface Props extends TextProps { + className?: string; + children: string | React.ReactNode; +} + +export const NText = (props: Props) => { + const { children, className, ...rest } = props; + return ( + + {children} + + ); }; diff --git a/src/components/NTextarea.tsx b/src/components/NTextarea.tsx new file mode 100644 index 0000000..016738a --- /dev/null +++ b/src/components/NTextarea.tsx @@ -0,0 +1,33 @@ +import { type TextInputProps, View } from 'react-native'; +import { Textarea } from '@/components/ui/textarea'; +import { NText } from '@/components/NText'; +import { cn } from '@/lib/utils'; + +interface Props extends TextInputProps { + value: string; + label?: string; + placeholder?: string; + disabled?: boolean; + className?: string; + onChangeText: (text: string) => void; +} + +export const NTextarea = (props: Props) => { + const { value, label = '', placeholder = '', className = '', onChangeText, disabled = false, ...remaining } = props; + + return ( + + {label && {label}} +