Signed-off-by: rgrgogu <obsequio.rus@gmail.com>
This commit is contained in:
rgrgogu
2026-08-13 20:00:06 +08:00
parent 377a4d4b5e
commit 3c5432a32b
29 changed files with 104 additions and 1029 deletions
+61 -55
View File
@@ -3,7 +3,8 @@
* Type : Reusable Component
* Description : Combined date + time picker for deadline fields.
* Controlled via a single ISO datetime string (value / onChange).
* Date is picked via a Calendar popover; time via a plain time input.
* Calendar + time input live inside a Card, matching shadcn's
* "Date and Time Picker" pattern.
*
* Props:
* value : string | null — ISO datetime string e.g. "2026-06-01T10:30"
@@ -12,12 +13,13 @@
***********************************************************************************************************************************************************************/
import { useState } from 'react';
import { format, parseISO, isValid } from 'date-fns';
import { ChevronDownIcon } from 'lucide-react';
import { ChevronDownIcon, Clock2Icon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardFooter } from '@/components/ui/card';
import { Field, FieldGroup, FieldLabel } from '@/components/ui/field';
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
// ── Helpers ───────────────────────────────────────────────────────────────────
@@ -51,7 +53,6 @@ export default function DeadlinePicker({ value, onChange, disabled = false }) {
const handleDateSelect = (date) => {
onChange(buildISO(date, timeStr));
setOpen(false);
};
const handleTimeChange = (e) => {
@@ -61,62 +62,67 @@ export default function DeadlinePicker({ value, onChange, disabled = false }) {
const handleClear = () => onChange(null);
return (
<div className="flex flex-wrap items-end gap-3">
{/* ── Date picker ──────────────────────────────────────────────── */}
<div className="space-y-2">
<Label className="text-xs text-muted-foreground">Date</Label>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
disabled={disabled}
className="w-36 justify-between font-normal text-sm"
>
{selectedDate ? format(selectedDate, 'MMM d, yyyy') : 'Select date'}
<ChevronDownIcon className="h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
disabled={disabled}
className="w-56 justify-between font-normal text-sm"
>
{selectedDate
? `${format(selectedDate, 'MMM d, yyyy')} ${format(parseISO(buildISO(selectedDate, timeStr)), 'h:mm a')}`
: 'Select date'}
<ChevronDownIcon className="h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto border-0 bg-transparent p-0 shadow-none ring-0" align="start">
<Card size="sm" className="w-fit">
<CardContent>
<Calendar
mode="single"
selected={selectedDate}
captionLayout="dropdown"
defaultMonth={selectedDate}
onSelect={handleDateSelect}
disabled={disabled}
className="p-0"
/>
</PopoverContent>
</Popover>
</div>
{/* ── Time input ───────────────────────────────────────────────── */}
<div className="space-y-2">
<Label className="text-xs text-muted-foreground">Time</Label>
<Input
type="time"
value={timeStr}
onChange={handleTimeChange}
disabled={disabled || !selectedDate}
step="60"
className="w-28 appearance-none bg-background
[&::-webkit-calendar-picker-indicator]:hidden
[&::-webkit-calendar-picker-indicator]:appearance-none"
/>
</div>
{/* ── Clear ────────────────────────────────────────────────────── */}
{value && (
<Button
type="button"
variant="outline"
disabled={disabled}
onClick={handleClear}
>
Clear
</Button>
)}
</div>
</CardContent>
<CardFooter className="flex-col items-stretch gap-3 border-t bg-card">
<FieldGroup>
<Field orientation="horizontal">
<FieldLabel htmlFor="deadline-time">Time</FieldLabel>
<InputGroup>
<InputGroupInput
id="deadline-time"
type="time"
step="60"
value={timeStr}
onChange={handleTimeChange}
disabled={disabled || !selectedDate}
className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
<InputGroupAddon>
<Clock2Icon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
{value && (
<Button
type="button"
variant="outline"
size="sm"
disabled={disabled}
onClick={handleClear}
>
Clear
</Button>
)}
</CardFooter>
</Card>
</PopoverContent>
</Popover>
);
}