diff --git a/src/App.tsx b/src/App.tsx index 50b653a..7f1f5e7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,6 +20,7 @@ import DynamicDemo from "./pages/DynamicDemo"; import CanvasDemo from "./pages/CanvasDemo"; import MultiWindowDemo from "./pages/MultiWindowDemo"; import AdvancedButtonDemo from "./pages/AdvancedButtonDemo"; +import DatePickerDemo from "./pages/DatePickerDemo"; import CommsTarget from "./pages/CommsTarget"; import VehicleSimulator from "./pages/VehicleSimulator"; import ThreeDChess from "./pages/ThreeDChess"; @@ -52,6 +53,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/ui/date-picker.tsx b/src/components/ui/date-picker.tsx new file mode 100644 index 0000000..de9abfe --- /dev/null +++ b/src/components/ui/date-picker.tsx @@ -0,0 +1,261 @@ +import * as React from "react"; +import { format } from "date-fns"; +import { Calendar as CalendarIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; + +export interface DatePickerProps { + date?: Date; + onDateChange?: (date: Date | undefined) => void; + placeholder?: string; + disabled?: boolean; + className?: string; + id?: string; + "data-testid"?: string; + "aria-label"?: string; +} + +const DatePicker = React.forwardRef< + HTMLButtonElement, + DatePickerProps +>(({ + date, + onDateChange, + placeholder = "Pick a date", + disabled = false, + className, + id, + "data-testid": dataTestId, + "aria-label": ariaLabel, + ...props +}, ref) => { + return ( + + + + + + + + + ); +}); +DatePicker.displayName = "DatePicker"; + +export interface DateRangePickerProps { + dateRange?: { from: Date | undefined; to: Date | undefined }; + onDateRangeChange?: (range: { from: Date | undefined; to: Date | undefined }) => void; + placeholder?: string; + disabled?: boolean; + className?: string; + id?: string; + "data-testid"?: string; + "aria-label"?: string; +} + +const DateRangePicker = React.forwardRef< + HTMLButtonElement, + DateRangePickerProps +>(({ + dateRange, + onDateRangeChange, + placeholder = "Pick a date range", + disabled = false, + className, + id, + "data-testid": dataTestId, + "aria-label": ariaLabel, + ...props +}, ref) => { + return ( + + + + + + { + if (range) { + onDateRangeChange?.({ + from: range.from, + to: range.to, + }); + } + }} + numberOfMonths={2} + /> + + + ); +}); +DateRangePicker.displayName = "DateRangePicker"; + +export interface DatePickerWithPresetsProps { + date?: Date; + onDateChange?: (date: Date | undefined) => void; + placeholder?: string; + disabled?: boolean; + className?: string; + id?: string; + "data-testid"?: string; + "aria-label"?: string; + presets?: Array<{ + label: string; + value: () => Date; + }>; +} + +const DatePickerWithPresets = React.forwardRef< + HTMLButtonElement, + DatePickerWithPresetsProps +>(({ + date, + onDateChange, + placeholder = "Pick a date", + disabled = false, + className, + id, + "data-testid": dataTestId, + "aria-label": ariaLabel, + presets = [ + { + label: "Today", + value: () => new Date(), + }, + { + label: "Yesterday", + value: () => { + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + return yesterday; + }, + }, + { + label: "Last Week", + value: () => { + const lastWeek = new Date(); + lastWeek.setDate(lastWeek.getDate() - 7); + return lastWeek; + }, + }, + { + label: "Last Month", + value: () => { + const lastMonth = new Date(); + lastMonth.setMonth(lastMonth.getMonth() - 1); + return lastMonth; + }, + }, + ], + ...props +}, ref) => { + return ( + + + + + +
+
+
+
Quick Select
+
+ {presets.map((preset) => ( + + ))} +
+
+
+ +
+
+
+ ); +}); +DatePickerWithPresets.displayName = "DatePickerWithPresets"; + +export { DatePicker, DateRangePicker, DatePickerWithPresets }; diff --git a/src/pages/AdvancedButtonDemo.tsx b/src/pages/AdvancedButtonDemo.tsx index 6ff7f9d..2c0d058 100644 --- a/src/pages/AdvancedButtonDemo.tsx +++ b/src/pages/AdvancedButtonDemo.tsx @@ -298,7 +298,7 @@ const AdvancedButtonDemo = () => { return (
diff --git a/src/pages/CanvasDemo.tsx b/src/pages/CanvasDemo.tsx index 71bc475..cedfb2c 100644 --- a/src/pages/CanvasDemo.tsx +++ b/src/pages/CanvasDemo.tsx @@ -263,7 +263,7 @@ const CanvasDemo = () => { return (
diff --git a/src/pages/DatePickerDemo.tsx b/src/pages/DatePickerDemo.tsx new file mode 100644 index 0000000..d9c8d7e --- /dev/null +++ b/src/pages/DatePickerDemo.tsx @@ -0,0 +1,452 @@ +import { useState } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; +import { DatePicker, DateRangePicker, DatePickerWithPresets } from "@/components/ui/date-picker"; +import { format } from "date-fns"; +import { ModuleHeader } from "@/components/ModuleHeader"; +import { useToast } from "@/hooks/use-toast"; + +const DatePickerDemo = () => { + const { toast } = useToast(); + + // Basic DatePicker state + const [selectedDate, setSelectedDate] = useState(); + const [disabledDate, setDisabledDate] = useState(); + + // Date Range state + const [dateRange, setDateRange] = useState<{ from: Date | undefined; to: Date | undefined }>({ + from: undefined, + to: undefined, + }); + + // DatePicker with presets state + const [presetDate, setPresetDate] = useState(); + + // Form integration state + const [formData, setFormData] = useState({ + startDate: undefined as Date | undefined, + endDate: undefined as Date | undefined, + eventDate: undefined as Date | undefined, + }); + + // Validation state + const [validationErrors, setValidationErrors] = useState([]); + + const validateForm = () => { + const errors: string[] = []; + + // Check if end date is before start date + if (formData.startDate && formData.endDate && formData.endDate < formData.startDate) { + errors.push("End date must be after start date"); + } + + // Check if event date is in the past + if (formData.eventDate && formData.eventDate < new Date()) { + errors.push("Event date cannot be in the past"); + } + + setValidationErrors(errors); + return errors.length === 0; + }; + + const handleFormSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm()) { + toast({ + title: "Form Validation Failed", + description: "Please correct the validation errors below", + variant: "destructive", + }); + return; + } + + const selectedDates = { + start: formData.startDate ? format(formData.startDate, "PPP") : "Not selected", + end: formData.endDate ? format(formData.endDate, "PPP") : "Not selected", + event: formData.eventDate ? format(formData.eventDate, "PPP") : "Not selected", + }; + + toast({ + title: "Form Submitted Successfully!", + description: `Start: ${selectedDates.start} | End: ${selectedDates.end} | Event: ${selectedDates.event}`, + }); + }; + + const clearAllDates = () => { + setSelectedDate(undefined); + setDisabledDate(undefined); + setDateRange({ from: undefined, to: undefined }); + setPresetDate(undefined); + setFormData({ + startDate: undefined, + endDate: undefined, + eventDate: undefined, + }); + setValidationErrors([]); + + toast({ + title: "All Dates Cleared", + description: "All date selections have been reset", + }); + }; + + return ( +
+ + +
+ +
+ {/* Basic DatePicker */} + + + + Basic Date Picker + + + Simple date selection with calendar popup + + + +
+
+ + +
+ +
+ + +
+
+ + + +
+
Selected Date:
+
+ {selectedDate ? format(selectedDate, "EEEE, MMMM do, yyyy") : "No date selected"} +
+
+
+
+ + {/* Date Range Picker */} + + + + Date Range Picker + + + Select start and end dates for date ranges + + + +
+ + +
+ + + +
+
Selected Range:
+
+ {dateRange.from ? ( + dateRange.to ? ( + `${format(dateRange.from, "MMM dd, yyyy")} - ${format(dateRange.to, "MMM dd, yyyy")}` + ) : ( + `From: ${format(dateRange.from, "MMM dd, yyyy")}` + ) + ) : ( + "No date range selected" + )} +
+
+
+
+ + {/* DatePicker with Presets */} + + + + Date Picker with Presets + + + Quick date selection with common presets + + + +
+ + +
+ + + +
+
Selected Date:
+
+ {presetDate ? format(presetDate, "EEEE, MMMM do, yyyy") : "No date selected"} +
+
+
+
+ + {/* Form Integration */} + + + + Form Integration + + + Date pickers integrated in a form context + + + +
+ {validationErrors.length > 0 && ( +
+
VALIDATION ERRORS:
+
    + {validationErrors.map((error, index) => ( +
  • • {error}
  • + ))} +
+
+ )} +
+
+ + setFormData(prev => ({ ...prev, startDate: date }))} + placeholder="Select start date..." + /> +
+ +
+ + setFormData(prev => ({ ...prev, endDate: date }))} + placeholder="Select end date..." + /> +
+ +
+ + setFormData(prev => ({ ...prev, eventDate: date }))} + placeholder="Select event date..." + /> +
+
+ + + +
+ + +
+ +
+
+
+ + {/* Control Panel */} + + + + Control Panel + + + Global controls for testing date picker interactions + + + +
+ + + + + + + +
+
+
+ + {/* Mission Summary */} + + + MISSION SUMMARY + Date selection systems status and calendar interface metrics + + +
+
+

DATE PICKER STATUS:

+
    +
  • Basic Date Picker: {selectedDate ? "ACTIVE" : "STANDBY"}
  • +
  • Date Range Picker: {dateRange.from ? "ACTIVE" : "STANDBY"}
  • +
  • Preset Date Picker: {presetDate ? "ACTIVE" : "STANDBY"}
  • +
  • Form Integration: {formData.startDate || formData.endDate || formData.eventDate ? "ACTIVE" : "STANDBY"}
  • +
+
+
+

SYSTEM METRICS:

+
    +
  • Interface Status: OPERATIONAL
  • +
  • Date Selections: {[selectedDate, presetDate, formData.startDate, formData.endDate, formData.eventDate].filter(Boolean).length}
  • +
  • Range Status: {dateRange.from && dateRange.to ? "COMPLETE" : dateRange.from ? "PARTIAL" : "EMPTY"}
  • +
  • Form State: {Object.values(formData).some(date => date) ? "POPULATED" : "EMPTY"}
  • +
+
+
+ +
+

TEST PROTOCOLS:

+
+
+
    +
  • • Calendar popup activation and trigger testing
  • +
  • • Basic date selection and display
  • +
  • • Date range selection protocols
  • +
  • • Preset date quick selection
  • +
+
+
+
    +
  • • Form integration and submission
  • +
  • • Form validation and error handling
  • +
  • • Disabled state handling
  • +
  • • Clear and reset functionality
  • +
+
+
+
+
+
+ +
+
+ ); +}; + +export default DatePickerDemo; diff --git a/src/pages/DragDropDemo.tsx b/src/pages/DragDropDemo.tsx index 5c02885..2073e22 100644 --- a/src/pages/DragDropDemo.tsx +++ b/src/pages/DragDropDemo.tsx @@ -115,7 +115,7 @@ const DragDropDemo = () => { return (
diff --git a/src/pages/DynamicDemo.tsx b/src/pages/DynamicDemo.tsx index 6782219..ad32e2c 100644 --- a/src/pages/DynamicDemo.tsx +++ b/src/pages/DynamicDemo.tsx @@ -157,7 +157,7 @@ const DynamicDemo = () => { return (
diff --git a/src/pages/FramesDemo.tsx b/src/pages/FramesDemo.tsx index 86ec891..8d07531 100644 --- a/src/pages/FramesDemo.tsx +++ b/src/pages/FramesDemo.tsx @@ -53,7 +53,7 @@ const FramesDemo = () => { return (
diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index eb0d414..37c6eaa 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -19,7 +19,7 @@ const Index = () => { "/button-demo", "/text-input-demo", "/login-demo", "/dropdown-demo", "/checkbox-radio-demo", "/table-demo", "/modal-demo", "/alert-demo", "/file-upload-demo", "/drag-drop-demo", "/frames-demo", "/dynamic-demo", - "/canvas-demo", "/multi-window-demo", "/advanced-button-demo" + "/canvas-demo", "/multi-window-demo", "/advanced-button-demo", "/date-picker-demo" ]; const demoPages = [ @@ -89,6 +89,13 @@ const Index = () => { difficulty: "Intermediate", elements: ["file inputs", "file upload", "file validation"], implemented: true + }, { + title: "Date Picker Demo", + path: "/date-picker-demo", + description: "Test date selection, range picking, and calendar interactions", + difficulty: "Intermediate", + elements: ["date pickers", "calendar popups", "date ranges", "form integration", "date validation"], + implemented: true }, // Advanced Elements { diff --git a/src/pages/MultiWindowDemo.tsx b/src/pages/MultiWindowDemo.tsx index af821f6..1082891 100644 --- a/src/pages/MultiWindowDemo.tsx +++ b/src/pages/MultiWindowDemo.tsx @@ -228,7 +228,7 @@ const MultiWindowDemo = () => { return (