{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "date-picker", "type": "registry:ui", "title": "Date Picker", "description": "Input trigger opening a calendar modal.", "registryDependencies": [ "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/utils.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/text.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/calendar.json" ], "dependencies": [ "lucide-react-native" ], "files": [ { "path": "registry/shared/ui/date-picker.tsx", "type": "registry:ui", "target": "components/ui/date-picker.tsx", "content": "import { useContext, useMemo, useState } from 'react';\nimport { Modal, Pressable, StyleSheet, type PressableProps } from 'react-native';\nimport { Calendar as CalendarIcon } from 'lucide-react-native';\nimport {\n Calendar,\n type CalendarViewMode,\n type DateRange,\n} from '@/components/ui/calendar';\nimport { Text } from '@/components/ui/text';\nimport { cn, FormFieldContext, useIconColor, useThemeColor } from '@/lib/utils';\n\nexport type DateFormatPattern =\n | 'DD/MM/YYYY'\n | 'YYYY-MM-DD'\n | 'MM/DD/YYYY'\n | 'DD-MM-YYYY'\n | 'DD.MM.YYYY'\n | (string & {})\n | ((date: Date) => string);\n\nexport function formatDate(\n date?: Date,\n fmt?: DateFormatPattern\n): string {\n if (!date) return '';\n if (typeof fmt === 'function') {\n return fmt(date);\n }\n const day = String(date.getDate()).padStart(2, '0');\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const year = String(date.getFullYear());\n\n switch (fmt?.toUpperCase()) {\n case 'DD/MM/YYYY':\n return `${day}/${month}/${year}`;\n case 'YYYY-MM-DD':\n return `${year}-${month}-${day}`;\n case 'MM/DD/YYYY':\n return `${month}/${day}/${year}`;\n case 'DD-MM-YYYY':\n return `${day}-${month}-${year}`;\n case 'DD.MM.YYYY':\n return `${day}.${month}.${year}`;\n default:\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n }\n}\n\nexport interface DatePickerProps\n extends Omit {\n value?: Date;\n onChange?: (date: Date) => void;\n placeholder?: string;\n format?: DateFormatPattern;\n view?: CalendarViewMode;\n showOutsideDays?: boolean;\n /** Mirrors shadcn `aria-invalid` — destructive trigger border. Auto-detected from FormField error. */\n invalid?: boolean;\n className?: string;\n}\n\nexport function DatePicker({\n value,\n onChange,\n placeholder = 'Pick a date',\n format,\n view,\n showOutsideDays = true,\n invalid,\n className,\n disabled,\n style,\n ...props\n}: DatePickerProps) {\n const [open, setOpen] = useState(false);\n const iconColor = useIconColor();\n const colors = useThemeColor();\n const field = useContext(FormFieldContext);\n const isInvalid = invalid ?? !!field?.error;\n\n return (\n <>\n setOpen(true)}\n className={cn(\n 'h-11 flex-row items-center justify-between rounded-md border border-input bg-background px-3 active:bg-accent/40 dark:bg-input/30',\n disabled && 'opacity-50',\n className\n )}\n style={(state) => [\n { borderCurve: 'continuous' },\n isInvalid && { borderColor: colors.destructive },\n typeof style === 'function' ? style(state) : style,\n ]}\n {...props}\n >\n \n {value ? formatDate(value, format) : placeholder}\n \n \n \n setOpen(false)}\n >\n setOpen(false)}\n >\n e.stopPropagation()}\n style={styles.modalCard}\n >\n {\n onChange?.(d);\n setOpen(false);\n }}\n />\n \n \n \n \n );\n}\n\nexport interface DateRangePickerProps\n extends Omit {\n value?: DateRange;\n onChange?: (range: DateRange) => void;\n placeholder?: string;\n format?: DateFormatPattern;\n view?: CalendarViewMode;\n showOutsideDays?: boolean;\n invalid?: boolean;\n className?: string;\n}\n\n/**\n * Single input trigger for selecting a start and end date span on 1 calendar.\n */\nexport function DateRangePicker({\n value,\n onChange,\n placeholder = 'Pick a date range',\n format,\n view,\n showOutsideDays = true,\n invalid,\n className,\n disabled,\n style,\n ...props\n}: DateRangePickerProps) {\n const [open, setOpen] = useState(false);\n const iconColor = useIconColor();\n const colors = useThemeColor();\n const field = useContext(FormFieldContext);\n const isInvalid = invalid ?? !!field?.error;\n\n const { start, end } = value ?? {};\n\n const displayLabel = useMemo(() => {\n if (start && end) {\n return `${formatDate(start, format)} - ${formatDate(end, format)}`;\n }\n if (start) {\n return `${formatDate(start, format)} - Select end date`;\n }\n return placeholder;\n }, [start, end, format, placeholder]);\n\n return (\n <>\n setOpen(true)}\n className={cn(\n 'h-11 flex-row items-center justify-between rounded-md border border-input bg-background px-3 active:bg-accent/40 dark:bg-input/30',\n disabled && 'opacity-50',\n className\n )}\n style={(state) => [\n { borderCurve: 'continuous' },\n isInvalid && { borderColor: colors.destructive },\n typeof style === 'function' ? style(state) : style,\n ]}\n {...props}\n >\n \n {displayLabel}\n \n \n \n\n setOpen(false)}\n >\n setOpen(false)}\n >\n e.stopPropagation()}\n style={styles.modalCard}\n >\n {\n onChange?.(newRange);\n if (newRange.start && newRange.end) {\n setTimeout(() => setOpen(false), 200);\n }\n }}\n />\n \n \n \n \n );\n}\n\nconst styles = StyleSheet.create({\n modalBackdrop: {\n flex: 1,\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 20,\n },\n modalCard: {\n width: '100%',\n maxWidth: 360,\n },\n});\n" } ] }