Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -52,6 +53,7 @@ const App = () => (
<Route path="/canvas-demo" element={<CanvasDemo />} />
<Route path="/multi-window-demo" element={<MultiWindowDemo />} />
<Route path="/advanced-button-demo" element={<AdvancedButtonDemo />} />
<Route path="/date-picker-demo" element={<DatePickerDemo />} />
<Route path="/vehicle-simulator" element={<VehicleSimulator />} />
<Route path="/3d-chess" element={<ThreeDChess />} />
<Route path="/comms-target" element={<CommsTarget />} />
Expand Down
261 changes: 261 additions & 0 deletions src/components/ui/date-picker.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Popover>
<PopoverTrigger asChild>
<Button
ref={ref}
id={id}
data-testid={dataTestId}
aria-label={ariaLabel || `Select date${date ? `: ${format(date, "PPP")}` : ""}`}
variant={"outline"}
className={cn(
"w-full justify-start text-left font-normal",
!date && "text-muted-foreground",
className
)}
disabled={disabled}
{...props}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>{placeholder}</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={onDateChange}
initialFocus
/>
</PopoverContent>
</Popover>
);
});
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 (
<Popover>
<PopoverTrigger asChild>
<Button
ref={ref}
id={id}
data-testid={dataTestId}
aria-label={ariaLabel || `Select date range${dateRange?.from ? `: ${format(dateRange.from, "PPP")}${dateRange.to ? ` to ${format(dateRange.to, "PPP")}` : ""}` : ""}`}
variant={"outline"}
className={cn(
"w-full justify-start text-left font-normal",
!dateRange?.from && "text-muted-foreground",
className
)}
disabled={disabled}
{...props}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{dateRange?.from ? (
dateRange.to ? (
<>
{format(dateRange.from, "LLL dd, y")} -{" "}
{format(dateRange.to, "LLL dd, y")}
</>
) : (
format(dateRange.from, "LLL dd, y")
)
) : (
<span>{placeholder}</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
initialFocus
mode="range"
defaultMonth={dateRange?.from}
selected={dateRange}
onSelect={(range) => {
if (range) {
onDateRangeChange?.({
from: range.from,
to: range.to,
});
}
}}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
);
});
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 (
<Popover>
<PopoverTrigger asChild>
<Button
ref={ref}
id={id}
data-testid={dataTestId}
aria-label={ariaLabel || `Select date${date ? `: ${format(date, "PPP")}` : ""}`}
variant={"outline"}
className={cn(
"w-full justify-start text-left font-normal",
!date && "text-muted-foreground",
className
)}
disabled={disabled}
{...props}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>{placeholder}</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<div className="flex">
<div className="border-r">
<div className="p-3">
<div className="text-sm font-medium mb-2">Quick Select</div>
<div className="space-y-1">
{presets.map((preset) => (
<Button
key={preset.label}
variant="ghost"
size="sm"
className="w-full justify-start text-xs"
onClick={() => onDateChange?.(preset.value())}
>
{preset.label}
</Button>
))}
</div>
</div>
</div>
<Calendar
mode="single"
selected={date}
onSelect={onDateChange}
initialFocus
/>
</div>
</PopoverContent>
</Popover>
);
});
DatePickerWithPresets.displayName = "DatePickerWithPresets";

export { DatePicker, DateRangePicker, DatePickerWithPresets };
2 changes: 1 addition & 1 deletion src/pages/AdvancedButtonDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ const AdvancedButtonDemo = () => {
return (
<div className="min-h-screen bg-background">
<ModuleHeader
moduleNumber="015"
moduleNumber="016"
title="ADVANCED BUTTON CLICKING"
description="AUTONOMOUS NAVIGATION CHALLENGES"
/>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/CanvasDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ const CanvasDemo = () => {
return (
<div className="min-h-screen bg-background">
<ModuleHeader
moduleNumber="013"
moduleNumber="014"
title="CANVAS DEMO"
description="REMOTE VEHICLE OPERATIONS & TERRAIN MAPPING"
/>
Expand Down
Loading