DateRangePicker
MoleculeTwo-month popover for picking a start → end date range. Shares the same Calendar core as DatePicker; locale-aware, fully keyboard navigable, with min/max/disabledDates. Pixel-identical React sibling at modules/ui/DatePicker/index.tsx.
Raporlama dönemi
Bitiş tarihi başlangıçtan sonra olmalı.
<%- include('modules/ui/DateRangePicker', {
label: 'Raporlama dönemi',
hint: 'Bitiş tarihi başlangıçtan sonra olmalı.'
}) %>
Booking window
<%- include('modules/ui/DateRangePicker', {
label: 'Booking window',
locale: 'en',
value: { start: '2026-06-01', end: '2026-06-15' }
}) %>
Campaign dates
Please pick both dates.
<%- include('modules/ui/DateRangePicker', {
label: 'Campaign dates',
locale: 'en',
required: true,
error: 'Please pick both dates.'
}) %>
Locked range
<%- include('modules/ui/DateRangePicker', {
label: 'Locked range',
locale: 'en',
value: { start: '2026-01-01', end: '2026-01-31' },
disabled: true
}) %>
Tatil tarihleri
<%- include('modules/ui/DateRangePicker', {
label: 'Tatil tarihleri',
locale: 'tr',
value: { start: '2026-07-01', end: '2026-07-14' },
clear_label: 'Temizle'
}) %>
<%# modules/ui/DatePicker/DateRangePicker.ejs %>
<%#
Popover-based DateRangePicker — two-month side-by-side calendar grid.
Locals (all optional unless noted):
id, label, hint, error, disabled, required, className
value: { start, end } — strings (ISO yyyy-mm-dd) or Date instances
name_start / name_end — hidden input names for native form posting
min / max — bounds for both calendars
locale 'tr' | 'en' (default 'tr')
format — display format token string
TODO M2: presets + compareToPreviousPeriod.
%>
<%
var _localeCode = locals.locale || 'tr';
var _defaults = {
tr: {
placeholder: 'GG.AA.YYYY', format: 'DD.MM.YYYY',
clear: 'Aralığı temizle', today: 'Bugün', dialog: 'Tarih aralığı seçin',
prev: 'Önceki ay', next: 'Sonraki ay'
},
en: {
placeholder: 'MM/DD/YYYY', format: 'MM/DD/YYYY',
clear: 'Clear range', today: 'Today', dialog: 'Choose date range',
prev: 'Previous month', next: 'Next month'
}
};
var _def = _defaults[_localeCode] || _defaults.tr;
function toIso(v) {
if (!v) return '';
if (typeof v === 'string') return v;
try {
var d = (v instanceof Date) ? v : new Date(v);
if (isNaN(d.getTime())) return '';
return d.toISOString().split('T')[0];
} catch (e) { return ''; }
}
var _id = locals.id || 'dr-' + Math.random().toString(36).substr(2, 9);
var _label = locals.label || '';
var _hint = locals.hint || '';
var _error = locals.error || '';
var _disabled = !!locals.disabled;
var _required = !!locals.required;
var _format = locals.format || _def.format;
var _placeholder = locals.placeholder || _def.placeholder;
var _clearLabel = locals.clear_label || _def.clear;
var _dialogLabel = locals.dialog_label || _def.dialog;
var _range = locals.value || {};
var _value = { start: toIso(_range.start), end: toIso(_range.end) };
var _min = toIso(locals.min);
var _max = toIso(locals.max);
var _hintId = (_hint && !_error) ? (_id + '-hint') : '';
var _errorId = _error ? (_id + '-error') : '';
var _ariaDescribedBy = [_hintId, _errorId].filter(function (x) { return !!x; }).join(' ');
var _doublePh = _placeholder + ' → ' + _placeholder;
%>
<% if (_label) { %>
<%= _label %><% if (_required) { %>(required)<% } %>
<% } %>
<%- include('partials/_calendar', {
_id: _id,
_mode: 'range',
_locale: _localeCode,
_format: _format,
_value: _value,
_placeholder: _doublePh,
_clearLabel: _clearLabel,
_dialogLabel: _dialogLabel,
_invalid: !!_error,
_disabled: _disabled,
_required: _required,
_min: _min,
_max: _max,
_ariaDescribedBy: _ariaDescribedBy,
_nameStart: locals.name_start || '',
_nameEnd: locals.name_end || ''
}) %>
<% if (_hintId) { %><%= _hint %>
<% } %>
<% if (_errorId) { %><%= _error %>
<% } %>