Emails
1 month ago
EngagementChart
1 month ago
MediaHub
1 month ago
Onboarding
1 month ago
Popup
1 month ago
Skeletons
1 week ago
WhatsNew
1 month ago
charts
1 month ago
test
1 month ago
AdminMenuSync.js
1 month ago
ChartEmptyState.js
1 month ago
ChooseDate.js
1 month ago
ColorPicker.js
1 month ago
ExtendPlugins.js
1 month ago
Filters.js
1 month ago
Link.js
1 month ago
Navbar.js
1 week ago
NoFound.js
1 month ago
PageHeader.js
1 month ago
PluginRecommendations.js
1 month ago
PostScheduleField.js
1 month ago
PrestoPlayerIcon.js
1 month ago
ProGateOverlay.js
1 month ago
QuickAccess.js
1 month ago
RankedTable.js
1 month ago
StatCard.js
1 month ago
TopMedia.js
1 month ago
TopPerformingMedia.js
1 month ago
TopUsers.js
1 month ago
TruncatedTitle.js
1 month ago
UpgradeNotice.js
1 month ago
UpgradeToPro.js
1 month ago
VideoModal.js
1 month ago
WelcomeBanner.js
1 month ago
ChooseDate.js
135 lines
| 1 | import React, { useRef, useState, useEffect } from 'react'; |
| 2 | import { __ } from '@wordpress/i18n'; |
| 3 | import { DatePicker, Input } from '@bsf/force-ui'; |
| 4 | import { Calendar, Trash2, ChevronDown } from 'lucide-react'; |
| 5 | import { format } from '../utils/dateUtils'; |
| 6 | |
| 7 | function parseToDate(value) { |
| 8 | if (!value) return null; |
| 9 | if (value instanceof Date) return isNaN(value.getTime()) ? null : value; |
| 10 | const d = new Date(String(value).replace(' ', 'T')); |
| 11 | return isNaN(d.getTime()) ? null : d; |
| 12 | } |
| 13 | |
| 14 | export default function ChooseDate({ |
| 15 | chosenDate, |
| 16 | onPickDate, |
| 17 | placeholder, |
| 18 | className = '', |
| 19 | size = 'md', |
| 20 | }) { |
| 21 | const [isOpen, setIsOpen] = useState(false); |
| 22 | const [dropdownStyle, setDropdownStyle] = useState({}); |
| 23 | const wrapperRef = useRef(null); |
| 24 | const triggerRef = useRef(null); |
| 25 | |
| 26 | const selectedDate = parseToDate(chosenDate); |
| 27 | |
| 28 | useEffect(() => { |
| 29 | function handleClickOutside(e) { |
| 30 | if (wrapperRef.current && !wrapperRef.current.contains(e.target)) { |
| 31 | setIsOpen(false); |
| 32 | } |
| 33 | } |
| 34 | if (isOpen) { |
| 35 | document.addEventListener('mousedown', handleClickOutside); |
| 36 | } |
| 37 | return () => document.removeEventListener('mousedown', handleClickOutside); |
| 38 | }, [isOpen]); |
| 39 | |
| 40 | const displayValue = selectedDate |
| 41 | ? format(selectedDate, 'MMM dd, yyyy') |
| 42 | : ''; |
| 43 | |
| 44 | const handleDateSelect = (date) => { |
| 45 | if (!date) return; |
| 46 | const d = date instanceof Date ? date : new Date(date); |
| 47 | if (isNaN(d.getTime())) return; |
| 48 | const year = d.getFullYear(); |
| 49 | const month = String(d.getMonth() + 1).padStart(2, '0'); |
| 50 | const day = String(d.getDate()).padStart(2, '0'); |
| 51 | if (typeof onPickDate === 'function') { |
| 52 | onPickDate(`${year}-${month}-${day}`); |
| 53 | } |
| 54 | setIsOpen(false); |
| 55 | }; |
| 56 | |
| 57 | const clearDate = (e) => { |
| 58 | e.preventDefault(); |
| 59 | e.stopPropagation(); |
| 60 | if (typeof onPickDate === 'function') { |
| 61 | onPickDate(''); |
| 62 | } |
| 63 | setIsOpen(false); |
| 64 | }; |
| 65 | |
| 66 | return ( |
| 67 | <div ref={wrapperRef} className={`relative ${className}`}> |
| 68 | <div className="flex items-center gap-1"> |
| 69 | <div |
| 70 | className="flex-1 cursor-pointer" |
| 71 | ref={triggerRef} |
| 72 | onClick={() => { |
| 73 | if (!isOpen && triggerRef.current) { |
| 74 | const rect = triggerRef.current.getBoundingClientRect(); |
| 75 | // DatePicker is ~340px tall; pad to 360 so we flip before the |
| 76 | // last row clips against the viewport. |
| 77 | const POPOVER_HEIGHT = 360; |
| 78 | const GAP = 4; |
| 79 | const spaceBelow = window.innerHeight - rect.bottom; |
| 80 | const spaceAbove = rect.top; |
| 81 | const flipUp = |
| 82 | spaceBelow < POPOVER_HEIGHT + GAP && |
| 83 | spaceAbove > spaceBelow; |
| 84 | const style = { |
| 85 | position: 'fixed', |
| 86 | left: rect.left, |
| 87 | zIndex: 99999, |
| 88 | }; |
| 89 | if (flipUp) { |
| 90 | style.bottom = window.innerHeight - rect.top + GAP; |
| 91 | } else { |
| 92 | style.top = rect.bottom + GAP; |
| 93 | } |
| 94 | setDropdownStyle(style); |
| 95 | } |
| 96 | setIsOpen((prev) => !prev); |
| 97 | }} |
| 98 | > |
| 99 | <Input |
| 100 | size={size} |
| 101 | value={displayValue} |
| 102 | readOnly |
| 103 | placeholder={placeholder || __('Select date', 'presto-player')} |
| 104 | prefix={<Calendar size={16} />} |
| 105 | suffix={<ChevronDown size={16} />} |
| 106 | className="cursor-pointer" |
| 107 | /> |
| 108 | </div> |
| 109 | {selectedDate && ( |
| 110 | <button |
| 111 | type="button" |
| 112 | onClick={clearDate} |
| 113 | className="p-1.5 text-gray-400 hover:text-red-500 transition-colors border-0 bg-transparent outline-none shrink-0" |
| 114 | title={__('Clear Date', 'presto-player')} |
| 115 | aria-label={__('Clear Date', 'presto-player')} |
| 116 | > |
| 117 | <Trash2 size={16} aria-hidden="true" /> |
| 118 | </button> |
| 119 | )} |
| 120 | </div> |
| 121 | |
| 122 | {isOpen && ( |
| 123 | <div style={dropdownStyle} className="rounded-lg shadow-lg"> |
| 124 | <DatePicker |
| 125 | selectionType="single" |
| 126 | selected={selectedDate} |
| 127 | onDateSelect={handleDateSelect} |
| 128 | isFooter={false} |
| 129 | /> |
| 130 | </div> |
| 131 | )} |
| 132 | </div> |
| 133 | ); |
| 134 | } |
| 135 |