Skip to content

Instantly share code, notes, and snippets.

@toomanyredirects
Created June 23, 2020 12:04
Show Gist options
  • Save toomanyredirects/745d38d35cc7dc86230eecb41204a8dc to your computer and use it in GitHub Desktop.
Save toomanyredirects/745d38d35cc7dc86230eecb41204a8dc to your computer and use it in GitHub Desktop.
Lightweight Vanilla Date Time Picker Calendar
<form class="presentation">
<fieldset>
<label for="date-calendar">Modal Calendar</label>
<input type="text" id="date-calendar" data-datepicker="datetime" data-datepicker-mode="calendar" placeholder="Select date & time" required/>
</fieldset>
<fieldset>
<label for="datetime-local">Datetime-Local Input</label>
<input type="datetime-local" id="datetime-local" placeholder="Select date & time" required/>
</fieldset>
<fieldset>
<label for="date">Date Input</label>
<input type="date" id="date" placeholder="Select a date" required/>
<label for="time">Time Input</label>
<input type="time" id="time" placeholder="Select a time" required/>
</fieldset>
<fieldset>
<label for="month">Month Input</label>
<input type="month" id="month" placeholder="Select a month" required/>
</fieldset>
<fieldset>
<label for="dates-multi">Text Input with Multiple Dates</label>
<input type="text" id="dates-multi" data-datepicker="date" data-datepicker-mode="multiple" placeholder="Select multiple dates" required/>
</fieldset>
<fieldset>
<label for="years-range">Text Input with Year Range</label>
<input type="text" id="years-range" data-datepicker="year" data-datepicker-mode="range" placeholder="Select year range" required/>
</fieldset>
</form>
<script type="text/javascript">
// Lightweight Vanilla Date Time Picker Calendar
document.addEventListener('DOMContentLoaded', function(e) {
// Presentation variables
var options = {
disablePast: false,
disableFuture: false,
disableWeekend: true,
weekStart: 0,
minStep: 15,
hrsStep: 1
};
var translation = {
minutes: 'Minuten',
hours: 'Stunden',
months: ['Januar','Febuar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
days: ['Montag','Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'],
format: 'yyyy-MM-dd hh:mm', dateformat: 'yyyy-MM-dd', timeformat: 'hh:mm', monthformat: 'yyyy-MM', longformat: 'd. MMMM yyyy', displayformat: 'D, d. MMMM yyyy',
calendar: 'Kalender',
previous: 'vorheriger',
next: 'nächster',
selected: 'ausgewählt',
select: 'auswählen',
today: 'heute',
set: 'wählen',
now: 'jetzt'
};
// Options / translation de-CH:
Datepicker(options, translation);
// Default initialization
// Datepicker();
// Open calendar
document.querySelector('form input').click();
}, false);
</script>

Lightweight Vanilla Date Time Picker Calendar

A lightweight, accessible vanilla date time picker with calendar functionality, overwriting the user agent's default behavior.

A Pen by Dan on CodePen.

License.

// Lightweight Vanilla Date Time Picker Calendar
// ---------------------------------------------
//
// A lightweight, accessible vanilla date time picker with calendar functionality, overwriting
// the user agent's default date/week/month/time input behaviors.
//
// Compatibility: Javascript ES5, IE9+, Element.classList.add/remove/toggle omittet,
// no css flexbox used
//
// Autor: dirkdigweed@gmx.net
// Created: 2020.06.11, 17:00
// Changed: 2020.06.21, 09:05
function Datepicker (opt, lang, dts) {
var w = window, d = document,
options = { disablePast: false, disableFuture: false, disableWeekend: true, weekStart: 0, minStep: 15, hrsStep: 1 },
i18n = {
minutes: 'minutes',
hours: 'hours',
months: ['January','Febuary','March','April','May','June','July','August','September','October','November','December'],
days: ['Monday','Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
format: 'yyyy-MM-dd hh:mm', dateformat: 'yyyy-MM-dd', timeformat: 'hh:mm', monthformat: 'yyyy-MM', longformat: 'MMMM d, yyyy', displayformat: 'D, MMMM d yyyy',
calendar: 'calendar',
previous: 'previous',
next: 'next',
selected: 'selected',
select: 'select',
today: 'today',
set: 'set',
now: 'now'
},
keys = { esc: 27, space: 32, pgup: 33, pgdown: 34, end: 35, home: 36, left: 37, up: 38, right: 39, down: 40 },
icons = {
left: '<path d="M2.4,4l2.8-2.8L4.5,0.5l-4,4l4,4l0.7-0.7L2.4,5h6.1V4H2.4z"/>',
right: '<path d="M0.5,4v1h6.1L3.8,7.8l0.7,0.7l4-4l-4-4L3.8,1.2L6.6,4H0.5z"/>',
calendar: '<path d=""/>',
clock: '<path d=""/>'
};
// Merge options and language objects
if (opt) mergeObj(options, opt);
if (opt) mergeObj(i18n, lang);
// Layout template
var tpl = [
'<div class="datepicker-header"><h2 aria-live="polite">'+ i18n.calendar +'</h2><time aria-live="polite"></time></div>',
'<div class="datepicker-navigation" role="navigation">',
' <button type="button" class="prev" aria-label="'+ i18n.previous +'"><svg viewBox="0 0 9 9" role="illustration">'+ icons.left +'</svg></button>',
' <h3 id="datepicker-label" aria-live="polite" tabindex="0">Month Year</h3>',
' <button type="button" class="next" aria-label="'+ i18n.next +'"><svg viewBox="0 0 9 9" role="illustration">'+ icons.right +'</svg></button>',
'</div>',
'<div class="datepicker-body">',
' <form class="time">',
' <div class="hours"><label>'+ i18n.hours +'</label>',
' <button type="button" role="spinbutton">&plus;</button><input type="number" step="'+ options.hrsStep +'" min="0" max="23"/><button type="button" role="spinbutton">&minus;</button>',
' </div>',
' <div class="minutes"><label>'+ i18n.minutes +'</label>',
' <button type="button" role="spinbutton">&plus;</button><input type="number" step="'+ options.minStep +'" min="0" max="59"/><button type="button" role="spinbutton">&minus;</button>',
' </div>',
' </form>',
' <div class="month" role="grid" aria-labelledby="datepicker-label">',
' <div class="week" scope="col"></div>',
' <div class="days" scope="col"></div>',
' </div>',
' <div class="year"></div>',
' <div class="decade"></div>',
'</div>',
'<div class="datepicker-footer"><button type="button">'+ i18n.select +'</button></div>',
'<i class="indicator"></i>'
].join('');
// Trigger selectors
var triggers = d.querySelectorAll('[type*="date"], [type="month"], [type="time"], [type="week"], [data-datepicker]');
[].slice.call(triggers).forEach(function(trigger){ init(trigger); });
// Datepicker
function init (trigger) {
var picker, indicator, header, footer, navigation, title,
current, time, minutes, hours, days, week, month, year, decade,
label, pButton, nButton, aButton;
var type = trigger.getAttribute('type') !== 'text' ? trigger.getAttribute('type') : trigger.getAttribute('data-datepicker') || 'date';
type = type.split('-')[0].toLowerCase();
var mode = trigger.getAttribute('data-datepicker-mode') || 'single',
view = trigger.getAttribute('data-datepicker-view') || type,
offset = trigger.getAttribute('data-datepicker-offset') || 5,
date = new Date(), today = new Date(),
selected = trigger.value ? [formatDate(new Date(trigger.value), i18n.dateformat)] : [];
trigger.setAttribute('aria-haspopup', true);
trigger.setAttribute('aria-expanded', false);
// Trigger event listener
trigger.onfocus = prevent;
trigger.oninput = prevent;
trigger.addEventListener('click', toggle, false);
createPicker();
// Picker methods
function pick (e) {
if(e) e.preventDefault();
var el = this,
dt = new Date(el.firstChild.getAttribute('datetime')),
val, string = formatDate(dt, i18n.dateformat),
isSelected = el.hasAttribute('aria-selected');
switch(type) {
case 'datetime': val = formatDate(dt, i18n.format).replace(' ','T'); break;
case 'time': val = formatDate(dt, i18n.timeformat); break;
case 'month': val = formatDate(dt, i18n.monthformat); break;
default: val = string;
}
if (view.match(/(decade|year|month)/g)){
date.setFullYear(dt.getFullYear());
if (view === 'year') date.setMonth(dt.getMonth());
if (view === 'month') date.setDate(dt.getDate());
if (view === 'year' && type !== 'month') monthView();
else if (view === 'decade' && type !== 'year') yearView();
else if (view === 'month' && type === 'datetime') timeView();
else isSelected ? deselect(el, string, val) : select(el, string, val);
}
else isSelected ? deselect(el, string, val) : select(el, string, val);
}
function toggle (e) {
if(e) e.preventDefault();
var v = picker.className.match(/\bshow\b/g),
ops = d.querySelectorAll('.datepicker.show'),
fcs = d.querySelectorAll('input.focus');
[].slice.call(ops).forEach(function(o){ o.className = o.className.replace(/\bshow\b/g,'').trim(); });
[].slice.call(fcs).forEach(function(f){ f.className = f.className.replace(/\bfocus\b/g,'').trim(); });
picker.className = v ? picker.className.replace(/\bshow\b/g,'').trim() : picker.className +' show';
trigger.setAttribute('aria-expanded', v ? false : true);
trigger.className = v ? trigger.className.replace(/\bfocus\b/g,'').trim() : trigger.className +' focus';
place(picker, trigger);
if(mode.match(/(calendar|modal)/gi)) backdrop(v);
trigger.blur()
}
function select (el, string, val) {
if (!mode.match(/(multiple|range)/gi)) {
selected = [string];
[].slice.call(picker.querySelectorAll('[aria-selected]')).forEach(function(l){
l.removeAttribute('aria-selected');
});
trigger.value = val;
toggle();
}
else if (mode.match(/multiple/gi)) trigger.value = selected.toString();
else selected = mergeArr(selected, [string]);
el.title = ucFirst(i18n.selected);
el.setAttribute('aria-selected', true);
}
function deselect (el, string, val) {
if(string) selected = selected.filter(function(v, i, selected){ return v === string; });
if(!el || !mode.match(/(multiple|range)/gi)) selected.forEach(function(ref) {
el = picker.querySelector('[href="#'+ ref +'"]');
if(el) { el.removeAttribute('aria-selected'); el.removeAttribute('title'); }
trigger.value = '';
});
else { el.removeAttribute('aria-selected'); el.removeAttribute('title'); }
}
function disable (el) {
el.setAttribute('draggable', false);
el.setAttribute('disabled', '');
el.setAttribute('tabindex', '-1');
}
function enable (el) {
el.setAttribute('draggable', true);
el.removeAttribute('disabled', '');
el.onclick = pick;
el.ondrag = drag;
trigger.ondragover = over;
trigger.ondrop = drop;
function drag(e) { e.dataTransfer.dropEffect = 'link'; }
function over(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }
function drop(e) { e.preventDefault(); el.click(); }
}
function createPicker () {
picker = d.createElement('div');
picker.setAttribute('role', 'modal');
picker.setAttribute('aria-modal', true);
picker.setAttribute('aria-labelledby', 'datepicker-label');
picker.setAttribute('tabindex', 0);
picker.className = 'datepicker';
picker.innerHTML = tpl;
// Assign ui controls
header = picker.querySelector('.datepicker-header');
footer = picker.querySelector('.datepicker-footer');
navigation = picker.querySelector('.datepicker-navigation');
title = header.querySelector('time');
time = picker.querySelector('form.time');
minutes = time.querySelector('.minutes');
hours = time.querySelector('.hours');
month = picker.querySelector('.month');
week = month.querySelector('.week');
days = month.querySelector('.days');
year = picker.querySelector('.year');
decade = picker.querySelector('.decade');
label = navigation.querySelector('h3'),
nButton = navigation.querySelector('.next'),
pButton = navigation.querySelector('.prev'),
aButton = footer.querySelector('button'),
// Inject & call view
trigger.parentElement.appendChild(picker);
picker.addEventListener('keyup', keyup, false);
switch(type) {
case 'time': timeView(); break;
case 'month': yearView(); break;
case 'year': decadeView(); break;
default: monthView();
}
}
// Picker backdrop
function backdrop (hide){
picker.className += ' modal';
var drop = d.querySelector('.datepicker-backdrop');
if(!drop){ drop = d.createElement('div'); picker.insertAdjacentElement('afterend', drop); }
drop.className = 'datepicker-backdrop';
if(!hide) drop.onclick = function(e) { toggle(e); };
hide ? drop.setAttribute('hidden','') : drop.removeAttribute('hidden');
}
// Picker placing
function place(el, target) {
if (!el.className.match(/\bshow\b/g) || mode === 'calendar') return false;
var x, y, eP = pos(el), tP = pos(target), iP = indicator ? pos(indicator) : null,
sY = w.pageYOffset, sX = w.pageXOffset, vw = d.documentElement.clientWidth, vh = w.innerHeight;
x = tP.left + tP.width - eP.width - sX;
y = tP.top - eP.height - offset - sY;
el.className = el.className.replace(/\b(left|right|top|bottom)\b/g, '').trim();
if (x <= 0) {x = offset; el.className = el.className.replace(/\bleft\b/g,'').trim() +' right';}
if (y <= 0) {y = tP.top + tP.height + offset; el.className += ' bottom';}
if (x >= vw) el.className = el.className.replace(/\bright\b/g,'').trim() +' left';
if (y >= vh) el.className = el.className.replace(/\bbottom\b/g,'').trim();
el.setAttribute('style','left:'+ x +'px;top:'+ y +'px;');
// Overwite previous listeners
w.onscroll = function(){ place(el, target); };
w.onresize = function(){ place(el, target); };
d.onorientationchange = function(){ place(el, target); };
//Position method
function pos(el) {
var b = el.getBoundingClientRect();
return { top: b.top + w.pageYOffset, left: b.left + w.pageXOffset, width: b.width, height: b.height };
}
}
// Picker methods
function setClock () {
var mm = minutes.querySelector('input'),
hh = hours.querySelector('input');
mm.value = zero(date.getMinutes());
hh.value = zero(date.getHours());
spinner(mm);
spinner(hh);
time.addEventListener('change', set, false);
function set (e) {
if(type === 'time') trigger.value = hh.value +'-'+ mm.value +'-00';
trigger.value = hh.value +'-'+ mm.value;
}
function check (e) {
var val = parseInt(this.value, 10),
coe = parseInt(this.step, 10),
min = parseInt(this.min, 10),
max = parseInt(this.max, 10);
if (val < min) this.value = Math.ceil(max/coe)*coe;
else if (val > max) this.value = Math.ceil(min/coe)*coe;
else if (isNaN(val)) this.value = Math.ceil(min/coe)*coe;
this.value = zero(this.value);
}
function spinner (el) {
var step = parseInt(el.step, 10) || 1,
plus = el.parentNode.querySelector('button:first-of-type'),
minus = el.parentNode.querySelector('button:last-of-type');
plus.onclick = function(e) { change(el, step); };
minus.onclick = function(e) { change(el, -step); };
el.oninput = check;
el.onchange = check;
}
function change (el, num) {
el.value = zero(parseInt(el.value, 10) + num);
"createEvent" in d ? el.dispatchEvent(new Event('change')) : el.fireEvent('onchange');
}
function zero (int) {
return ('00'+ int).slice(-2);
}
}
function createDay (num, day, y) {
var el = d.createElement('a'),
ti = d.createElement('time'),
string = formatDate(date, i18n.dateformat),
match = date.toString() === today.toString();
ti.innerHTML = num;
el.href = '#'+ string;
ti.setAttribute('datetime', formatDate(date, i18n.format));
if (num === 1) {
if (day === 0) el.style.marginLeft = (6 * 14.28) + '%';
else el.style.marginLeft = ((day - 1) * 14.28) + '%';
}
if (options.disablePast && date.getTime() <= today.getTime() - 1) disable(el);
else if (options.disableWeekend && (day == 0 || day == 6)) disable(el);
else enable(el);
if (match) {
el.className += ' today';
el.title = ucFirst(i18n.today);
ti.setAttribute('datetime', formatDate(date, i18n.format));
}
if (selected.indexOf(string) > -1) el.setAttribute('aria-selected', true);
el.appendChild(ti);
days.appendChild(el);
}
function createWeekdays () {
week.innerHTML = '';
i18n.days.forEach(function(name) {
var el = d.createElement('abbr');
el.setAttribute('scope', 'col');
el.setAttribute('title', name);
el.setAttribute('data-content', name.substring(0, 1));
el.setAttribute('data-twochar', name.substring(0, 2));
el.setAttribute('data-threechar', name.substring(0, 3));
el.innerHTML = name;
week.appendChild(el);
});
}
function createMonths () {
year.innerHTML = '';
i18n.months.forEach(function(name, i) {
var a = d.createElement('a'),
ti = d.createElement('time'),
cur = new Date(date.getFullYear(), i+1, 0),
match = cur.getMonth() === today.getMonth() && cur.getFullYear() === today.getFullYear();
ti.innerHTML = name;
ti.setAttribute('datetime', formatDate(cur, i18n.monthformat));
a.href = '#'+ formatDate(cur, i18n.monthformat);
a.setAttribute('title', name +' '+ cur.getFullYear());
a.setAttribute('data-content', name.substring(0, 3));
if (match) a.className += ' current';
a.addEventListener('click', pick, false);
a.appendChild(ti);
year.appendChild(a);
});
}
function createYears (num) {
var years = [], span = yearSpan(date.getFullYear(), num);
for (var i = span[0]; i <= span[1]; i++) years.push(i);
decade.innerHTML = '';
years.forEach(function(name, i) {
var a = d.createElement('a'),
ti = d.createElement('time'),
cur = new Date(name, date.getMonth()+1, 0),
match = name === today.getFullYear();
ti.innerHTML = name;
ti.setAttribute('datetime', formatDate(cur, i18n.monthformat));
a.href = '#'+ formatDate(cur, i18n.monthformat);
if (match) a.className += ' current';
a.addEventListener('click', pick, false);
a.appendChild(ti);
decade.appendChild(a);
});
}
// Views
// View setter
function setView () {
var el = picker.querySelector('.'+ view),
active = el.querySelector('a:not([disabled]):first-of-type, .today:not([disabled]), [aria-selected]:not([disabled])'),
p = date.getMonth() - 1, pDate = new Date(date.getFullYear(), p, 0),
n = date.getMonth() + 1, nDate = new Date(date.getFullYear(), n, 0);
label.innerHTML = i18n.months[date.getMonth()] +' '+ date.getFullYear();
navigation.removeAttribute('hidden');
header.setAttribute('hidden','');
aButton.setAttribute('hidden','');
nButton.removeAttribute('hidden');
if(mode === 'calendar'){
var match = date.toString() === today.toString();
header.removeAttribute('hidden','');
title.innerHTML = formatDate(date, i18n.displayformat);
title.setAttribute('datetime', formatDate(date, i18n.format));
if (match) header.querySelector('h2').innerHTML = i18n.today;
}
if(view === 'month' && type === 'date'){
aButton.removeAttribute('hidden','');
}
if(view === 'year') {
p = date.getFullYear() - 1; pDate = new Date(p, 0, 0);
n = date.getFullYear() + 1; nDate = new Date(n, 0, 0);
label.innerHTML = date.getFullYear();
}
if(view === 'decade') {
p = yearSpan(date.getFullYear(), 10)[0] - 1; pDate = new Date(p, 0, 0);
n = yearSpan(date.getFullYear(), 10)[1] + 1; nDate = new Date(n, 0, 0);
label.innerHTML = (p+1) +' - '+ (n-1);
}
if(view === 'time') {
if(type === 'time') navigation.setAttribute('hidden','');
nButton.setAttribute('hidden','');
aButton.removeAttribute('hidden','');
label.innerHTML = formatDate(date, i18n.longformat);
}
// Reset event listeners
aButton.onclick = set;
pButton.onclick = previous;
nButton.onclick = next;
label.onclick = index;
// Define future / past dates
var isPast = nDate.getTime() < today.getTime(),
isFuture = pDate.getTime() > today.getTime();
// Set and display view
current = { name: view, el: el, items: el.querySelectorAll('a'), prev: pButton, next: nButton };
display();
// Methods
function display () {
var vs = picker.querySelectorAll('.show');
[].slice.call(vs).forEach(function(v){ v.className = v.className.replace(/\bshow\b/g,'').trim(); });
el.className = (el.className + ' show').trim();
if(view !== 'time') active ? active.focus() : current.items[0].focus();
}
function previous (e) {
if(options.disablePast && isPast) return false;
if(view === 'time') monthView();
else if(view === 'year') yearView(p);
else if(view === 'decade') decadeView(p);
else monthView(p);
}
function next (e) {
if(options.disableFuture && isFuture) return false;
if(view === 'year') yearView(n);
else if(view === 'decade') decadeView(n);
else monthView(n);
}
function index (e) {
if(view === 'decade') return false;
else if(view === 'year') decadeView();
else if(view === 'time') monthView();
else yearView();
}
function set (e) {
if(type.match(/(time|date)/gi)){
trigger.value = type === 'time' ? formatDate(date, i18n.timeformat) : formatDate(date, i18n.dateformat);
toggle();
}
}
}
// Views
function timeView (hrs, min) {
view = 'time';
if(hrs) date.setHours(hrs);
if(min) date.setMinutes(min);
setClock();
setView();
}
function monthView (num) {
view = 'month';
if(num) date.setMonth(num);
date.setDate(1);
var cur = date.getMonth();
createWeekdays();
days.innerHTML = '';
while (date.getMonth() === cur) {
createDay(date.getDate(), date.getDay(), date.getFullYear() );
date.setDate(date.getDate() + 1);
}
// while loop trips over and day is at 30/31, bring it back
date.setDate(1);
date.setMonth(date.getMonth() - 1);
setView();
}
function yearView (num) {
view = 'year';
if(num) date.setFullYear(num);
createMonths();
setView();
}
function decadeView (num) {
view = 'decade';
if(num) date.setFullYear(num);
createYears(10);
setView();
}
// Keyboard events
function keyup (e) {
var fl = false, s = view === 'month' ? 7 : 3,
el = current.el.querySelector(':not([disabled]):focus, [selected]:not([disabled])') || current.items[0],
index = [].indexOf.call(current.items, el);
switch (e.keyCode) {
case keys.esc: toggle(e); break;
case keys.space: if(el) el.click(); break;
case keys.right: el && el.nextSibling ? el.nextSibling.focus() : toNext; fl = true; break;
case keys.left: el && el.previousSibling ? el.previousSibling.focus() : toPrevious; fl = true; break;
case keys.down: typeof current.items[index+s] !== 'undefined' ? current.items[index+s].focus() : toNext; fl = true; break;
case keys.up: typeof current.items[index-s] !== 'undefined' ? current.items[index-s].focus() : toPrevious; fl = true; break;
case keys.pgup: toPrevious(); fl = true; break;
case keys.pgdown: toNext(); fl = true; break;
case keys.home: current.items[0].focus(); fl = true; break;
case keys.end: current.items[current.items.length - 1].focus(); fl = true; break;
}
if (fl) prevent;
// Methods
function toPrevious () { current.prev.click(); };
function toNext () { current.next.click(); };
}
}
// Helper
function prevent (e) {
e.stopPropagation(); e.preventDefault();
}
function mergeObj (o1, o2) {
for (var key in o2) o1[key] = o2[key]; return o1;
}
function mergeArr (a1, a2) {
return a1.concat(a2.filter(function (m) { return a1.indexOf(m) === -1; }));
}
function ucFirst (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function yearSpan (number, interval) {
var s = Math.floor(number/interval)*interval;
return [s, s+interval-1];
}
function formatDate (date, format) {
var z = { M: date.getMonth() + 1, d: date.getDate(), h: date.getHours(), m: date.getMinutes(), s: date.getSeconds() };
format = format.replace(/\b(M{1,2}|d{1,2}|h{1,2}|m{1,2}|s{1,2})\b/g, function(v) {
return ((v.length > 1 ? '0' : '') + z[v.slice(-1)]).slice(-2);
});
format = format.replace(/(y+)/g, function(v) {
return date.getFullYear().toString().slice(-v.length);
});
format = format.replace(/(D|ddd+)/g, i18n.days[date.getDay() - 1]);
format = format.replace(/(MMM+)/g, i18n.months[date.getMonth()]);
return format;
}
}
// Pen
// Font stacks
@import url(https://fonts.googleapis.com/css?family=Montserrat:200,400,700);
$font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif !default;
*, *::before, *::after {
box-sizing: border-box;
}
html, body {
height: 100%;
width: 100%;
}
body {
display: flex;
align-items: center;
justify-content: center;
margin: 0;
font-family: Montserrat, $font-stack;
color: #222;
}
.presentation {
&, fieldset {
border: 0; min-width: 15rem;
}
label {
line-height: 1;
padding: 0;
margin: 0 0 .5em 0;
white-space: nowrap;
float: left;
&:not(only-child) {
width: 60%;
}
& ~ label {
margin-top: -1.5em;
width: 35%;
float: right;
}
}
input {
width: 100%;
float: left;
margin: 0;
&[type="date"]:not(only-child) {
width: 60%;
}
&[type="time"]:not(only-child) {
width: 35%;
float: right;
}
}
}
// Lightweight Vanilla Date Time Picker
$datepicker-font-color: #222 !default;
$datepicker-bg: #fff !default;
$datepicker-dark-bg: #999 !default;
$datepicker-spacing: 1rem !default;
$datepicker-max-size: 29rem !default;
$datepicker-min-size: 20rem !default;
$datepicker-border-radius: .3rem !default;
$datepicker-border-color: #e7e9ed !default;
$datepicker-box-shaddow: 0 4px 22px 0 rgba(0, 0, 0, 0.05) !default;
$datepicker-header-bg: #109899 !default;
$datepicker-header-color: #fff !default;
$datepicker-today-bg: #109899 !default;
$datepicker-today-color: #fff !default;
$datepicker-today-indicator-bg: orange !default;
$datepicker-focus-color: #333 !default;
$datepicker-selected-bg: #E77 !default;
$datepicker-selected-color: #FFF !default;
$datepicker-zindex: 1020 !default;
$datepicker-breakpoint-sm: 360px !default;
$datepicker-breakpoint-md: 768px !default;
$datepicker-breakpoint-lg: 1098px !default;
$datepicker-breakpoint-height: 640px !default;
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index { @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); }
@return $string;
}
:root {
--ui-datepicker-bg: #{$datepicker-bg};
--ui-datepicker-border-radius: #{$datepicker-border-radius};
--ui-datepicker-border-color: #{$datepicker-border-color};
--ui-datepicker-today-bg: #{$datepicker-today-bg};
--ui-datepicker-today-color: #{$datepicker-today-color};
--ui-datepicker-selected-bg: #{$datepicker-selected-bg};
--ui-datepicker-selected-color: #{$datepicker-selected-color};
--ui-datepicker-zindex: #{$datepicker-zindex};
--ui-datepicker-breakpoint-sm: #{$datepicker-breakpoint-sm};
--ui-datepicker-breakpoint-md: #{$datepicker-breakpoint-md};
--ui-datepicker-breakpoint-lg: #{$datepicker-breakpoint-lg};
--ui-datepicker-breakpoint-height: #{$datepicker-breakpoint-height};
}
// Inputs
input {
appearance: none;
display: inline-block;
padding: .25em .5em;
font: inherit;
line-height: 1.5;
border: .075em solid currentColor;
border-radius: $datepicker-border-radius;
outline: 0;
transition: color .2s ease, box-shadow .2s ease, background .2s ease;
&::placeholder {
opacity: .5;
}
&:focus, &.focus {
color: $datepicker-today-bg;
box-shadow: 0 0 0 .2rem rgba($datepicker-today-bg, 0.25);
&::placeholder {
color: $datepicker-today-bg;
opacity: 1;
}
}
&::selection {
color: $datepicker-today-color;
background: $datepicker-today-bg;
}
&[type="time"],
&[type*="date"],
&[type="week"],
&[type="month"],
&[type="year"],
&[data-datepicker]{
background-repeat: no-repeat;
background-size: 1.25em 1.25em;
background-position: calc(100% - .55em) center;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 9 9'%3e%3cpath d='M1.8.5v.8h-.7c-.2 0-.4.2-.4.4v6.5c0 .2.2.4.4.4H8c.2 0 .4-.2.4-.4V1.6c0-.2-.2-.4-.4-.4h-.8V.5H6v.8H3V.5H1.8zm-.3 3h6.1v4.2H1.5V3.5zm.3.4v1.5h1.5V3.9H1.8zm1.9 0v1.5h1.5V3.9H3.7zm1.9 0v1.5h1.5V3.9H5.6zM1.8 5.8v1.5h1.5V5.8H1.8zm1.9 0v1.5h1.5V5.8H3.7z' fill='#{str-replace(''+$datepicker-font-color, '#', '%23')}'/%3e%3c/svg%3e");
cursor: default;
user-select: none!important;
&:focus, &.focus {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 9 9'%3e%3cpath d='M1.8.5v.8h-.7c-.2 0-.4.2-.4.4v6.5c0 .2.2.4.4.4H8c.2 0 .4-.2.4-.4V1.6c0-.2-.2-.4-.4-.4h-.8V.5H6v.8H3V.5H1.8zm-.3 3h6.1v4.2H1.5V3.5zm.3.4v1.5h1.5V3.9H1.8zm1.9 0v1.5h1.5V3.9H3.7zm1.9 0v1.5h1.5V3.9H5.6zM1.8 5.8v1.5h1.5V5.8H1.8zm1.9 0v1.5h1.5V5.8H3.7z' fill='#{str-replace(''+$datepicker-today-bg, '#', '%23')}'/%3e%3c/svg%3e");
}
&::-webkit-spin-button,
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
display: none;
}
&::-webkit-clear-button {
display: none;
}
&::-ms-clear {
display: none;
}
&::-webkit-calendar-picker-indicator {
right: 1.25em;
width: 1em;
opacity: 0;
pointer-events: none;
margin:0;
}
}
&[type="time"] {
/* autoprefixer: ignore next */
-moz-appearance: textfield;
}
&[type="time"],
&[data-datepicker-mode="time"]{
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 9 9'%3e%3cpath d='M4.5.5c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4zm1.8 5.2l-.4.1-.2-.1-1.4-1-.1-.1v-.1-.1-.1-.1L5 1.3c0-.2.2-.3.4-.2.2.1.3.3.3.5L5 4.3l1.2.8c.1.2.2.4.1.6z' fill='#{str-replace(''+$datepicker-font-color, '#', '%23')}'/%3e%3c/svg%3e");
&:focus, &.focus {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 9 9'%3e%3cpath d='M4.5.5c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4zm1.8 5.2l-.4.1-.2-.1-1.4-1-.1-.1v-.1-.1-.1-.1L5 1.3c0-.2.2-.3.4-.2.2.1.3.3.3.5L5 4.3l1.2.8c.1.2.2.4.1.6z' fill='#{str-replace(''+$datepicker-today-bg, '#', '%23')}'/%3e%3c/svg%3e");
}
}
}
// Picker
.datepicker {
position: absolute;
display: inline-block;
margin: 0;
overflow: visible;
width: $datepicker-min-size;
font-size: 1rem;
color: $datepicker-font-color;
background-color: $datepicker-bg;
border-radius: $datepicker-border-radius;
border: 1px solid $datepicker-border-color;
box-shadow: $datepicker-box-shaddow;
outline: none;
z-index: $datepicker-zindex;
opacity: 0;
will-change: opacity, transform;
transition: opacity .2s linear, transform .2s ease;
&, * { user-select: none; }
[hidden] { display: none!important; visibility: hidden; }
&:not(.show) { display: none; }
&.show {
display: block;
opacity: 1;
}
&.slide-in {
transition: opacity .2s ease, transform .2s linear;
&:not(.show) { opacity: 0; transform: translate(0, 0); }
}
&.modal {
position: absolute;
top: 10%; left: 50%;
transform: translateX(-50%);
overflow: visible;
border: 0;
width: $datepicker-min-size;
@media (min-width: $datepicker-breakpoint-md ) {
width: 2*$datepicker-min-size;
& > div {
display:block;
width: 50.5%;
float: right;
}
.datepicker-header {
position: absolute;
bottom: 0; top:0; left: 0;
height: 100%;
border-radius: $datepicker-border-radius 0 0 $datepicker-border-radius;
time {
position: absolute;
font-size: 1.2rem;
bottom: $datepicker-spacing;
}
}
.datepicker-navigation {
clear: left;
}
}
}
&-backdrop {
position: fixed;
background: rgba(0,0,0,.25);
left: 0; right: 0; top:0; bottom:0;
z-index: $datepicker-zindex - 1;
}
.indicator {
position: absolute;
top: auto;
right: auto;
bottom: -$datepicker-spacing/2;
left: calc(50% - #{$datepicker-spacing}/2);
display: inline-block;
color: $datepicker-bg;
border: $datepicker-spacing/2 solid transparent;
border-bottom: 0;
border-top: $datepicker-spacing/2 solid currentColor;
width: 0;
height: 0;
}
&.bottom .indicator {
top: -$datepicker-spacing/2;
bottom: auto;
border-top: 0;
border-bottom: $datepicker-spacing/2 solid currentColor;
}
&.left .indicator {
top: calc(50% - #{$datepicker-spacing}/2);
right: -$datepicker-spacing/2;
left: auto;
bottom: auto;
border-top: 0;
border-right: $datepicker-spacing/2 solid currentColor;
}
&.right .indicator {
top: calc(50% - #{$datepicker-spacing}/2);
right: auto;
left: -$datepicker-spacing/2;
bottom: auto;
border-top: 0;
border-left: $datepicker-spacing/2 solid currentColor;
}
button {
appearance: none;
min-width: 1rem;
overflow: visible;
padding: 0;
margin: 0;
border: 0;
background: transparent;
color: inherit;
font: inherit;
line-height: normal;
text-align: center;
cursor: pointer;
outline: none;
border-radius: $datepicker-border-radius;
&:first-letter {
text-transform: uppercase;
}
&:hover {
color: $datepicker-today-bg;
}
&:active {
background-color: $datepicker-today-bg;
}
&:focus {
color: white;
background-color: $datepicker-today-bg;
box-shadow: 0 0 0 2px rgba($datepicker-today-bg, 0.1);
}
svg {
fill: currentColor;
}
}
&-header, &-navigation, &-body, &-footer {
display: inline-block;
width: 100%;
margin: 0;
padding: $datepicker-spacing;
}
&-header {
background: $datepicker-header-bg;
border-radius: $datepicker-border-radius $datepicker-border-radius 0 0 ;
h2, time {
color: $datepicker-header-color;
&:first-letter {
text-transform: uppercase;
}
}
h2 {
font-weight: 400;
width: 100%;
font-variant-numeric: ordinal;
margin: 0 0 2rem 0;
}
time {
color: $datepicker-header-color;
font-weight: 200;
font-size: 1.15rem;
}
&:not([hidden]) ~ .indicator {
display: none;
}
}
&-navigation {
position: relative;
button {
position: absolute;
width: 1.5rem; height: 1.5rem;
padding: .25rem;
line-height:1;
top: $datepicker-spacing*1.05;
&.prev { left: $datepicker-spacing; }
&.next { right: $datepicker-spacing;}
svg {
width:1rem; height: 1rem;
}
}
h3 {
margin: 0 auto;
padding: 0;
line-height: 1.5;
text-align: center;
border-radius: $datepicker-border-radius;
font-size: 1.1em;
width: calc(100% - 4em);
cursor: pointer;
font-variant-numeric: tabular-nums;
outline: 0;
&:hover {
color: $datepicker-today-bg;
}
&:focus {
color: $datepicker-today-color;
background-color: $datepicker-today-bg;
box-shadow: 0 0 0 2px rgba($datepicker-today-bg, 0.1);
}
}
}
&-body {
padding-bottom: 0;
}
&-footer {
text-align: center;
}
.time, .month, .year, .decade {
display: none;
&.show { display: block; }
}
.week, .days {
display: inline-block;
margin: 0; padding:0;
font-size: 0.9em;
abbr, a {
display: inline-block;
overflow: hidden;
width: 14.28%;
max-width: 14.28%;
margin: 0;
text-decoration: none;
text-align: center;
}
}
.week {
padding-bottom: 0;
abbr {
position: relative;
padding: 0;
overflow: hidden;
color: transparent;
&:first-letter {
text-transform: uppercase;
}
&::before {
position: absolute;
left: 0;
display: inline-block;
width: 100%;
content: attr(data-content);
color: rgba(128,128,128,0.25);
text-align: center;
text-transform: uppercase;
}
}
}
.days {
a {
line-height: 2;
&::after {
content: none;
position: absolute;
bottom: 0; right: .25rem;
display: inline-block;
width: 8px; height: 8px;
border-radius: 50%;
border: 1px solid $datepicker-bg;
background: $datepicker-selected-bg;
}
&.today::after {
content: '';
background: $datepicker-today-indicator-bg;
}
time {
width: 2rem; height: 2rem;
border-radius: 50%;
line-height: 2.2;
letter-spacing: -.1em;
}
}
}
.time {
font-size: 2rem;
white-space: nowrap;
div {
position: relative;
display: inline-block;
width: 50%;
text-align: center;
&:first-child {
float: left;
&::after {
content: ':';
position: absolute;
width: 1em; height: 1.5em;
right: -.5em; top: calc(50% - .25em);
line-height: 1.5;
}
}
&:last-child {float: right;}
}
label {
display: block;
width: 100%;
font-size: .5em;
margin: 0 0 1rem 0;
&:first-letter {
text-transform: uppercase;
}
}
button, input {
width: 2.5em;
line-height: 1;
margin: 0 auto;
font: inherit;
color: currentColor;
text-align: center;
float: none;
user-drag: none;
}
button {
display: block;
text-decoration: none;
&:first-of-type {
border-radius: $datepicker-border-radius $datepicker-border-radius 0 0;
}
&:last-of-type {
border-radius: 0 0 $datepicker-border-radius $datepicker-border-radius;
}
}
input {
font-variant-numeric: tabular-nums;
appearance: none;
/* autoprefixer: ignore next */
-moz-appearance: textfield;
border: 0;
outline: 0;
&:focus {
}
&::-webkit-spin-button,
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
display: none;
margin: 0;
}
}
}
.year, .decade {
a {
line-height: 1;
width: 33.333%;
time {
width: 100%; height: 100%;
padding: 1.5rem .25rem;
font-size: .9em;
letter-spacing: 0;
border-radius: $datepicker-border-radius;
}
}
}
.decade {
a:last-child { margin: 0 33.333%; }
}
.days a, .year a, .decade a {
position: relative;
display: inline-block;
padding: 0;
color: currentColor;
text-align: center;
outline: none;
background: transparent!important;
&:hover time, &:focus time {
color: $datepicker-focus-color;
background-color: rgba($datepicker-focus-color, 0.1);
}
time {
display: inline-block;
font-variant-numeric: slashed-zero tabular-nums;
}
&.today, &.current {
time {
font-weight: bold;
background-color: $datepicker-today-bg;
color: $datepicker-today-color;
}
}
&[aria-selected] {
time {
background-color: $datepicker-selected-bg;
color: $datepicker-selected-color;
}
&.start, &.end {
&::before {
content: '';
position: absolute;
height: 100%; width: 50%;
background-color: lighten($datepicker-selected-bg, 22);
z-index: -1;
}
}
&.start {
&::before { right: 0; }
time { border-radius:50% 0 0 50%; }
& ~ a {
background-color: lighten($datepicker-selected-bg, 22);
color: $datepicker-selected-bg;
}
}
&.end {
background-color: inherit!important;
&::before { left: 0; }
time { border-radius:0 50% 50% 0; }
& ~ a {
background-color: inherit;
color: inherit;
}
}
}
&[disabled]{
border-radius: 0;
pointer-events: none;
cursor: not-allowed;
opacity: 0.5;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment