A Pen by Sam Woolerton on CodePen.
Created
February 14, 2022 15:48
-
-
Save version-control/a9385c52d9c1d6b152e411d5209751e1 to your computer and use it in GitHub Desktop.
Journey - Date picker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link href="https://fonts.googleapis.com/css?family=Coda|Open+Sans" rel="stylesheet"> | |
<div id="date-picker-section" class="container"> | |
<div id="date-picker-container"> | |
<div id="date-picker-title"> | |
<h4> Select your dates </h4> | |
</div> | |
<p> Click on a date to open the date picker. </p> | |
<div id="date-picker-dates"> | |
<div id="date-picker-date-first" class="date-picker-date"> | |
24/12/2017 | |
</div> | |
<div class="date-picker-date"> | |
28/12/2017 | |
</div> | |
</div> | |
<p> Revel in how big the dates are now. </p> | |
<div id="date-picker-display-container"> | |
<div class="date-picker-display-pair"> | |
<div class="date-picker-display"> | |
24 Dec | |
</div> | |
<div class="date-picker-display-label"> | |
Date one | |
</div> | |
</div> | |
<div class="date-picker-display-pair"> | |
<div class="date-picker-display"> | |
28 Dec | |
</div> | |
<div class="date-picker-display-label"> | |
Date two | |
</div> | |
</div> | |
</div> | |
<div id="date-picker-modal" class="hidden-2"> | |
<div id="date-picker-top-bar"> | |
<div id="date-picker-previous-month" class="date-picker-change-month">‹</div> | |
<div id="date-picker-month">December 17</div> | |
<div id="date-picker-next-month" class="date-picker-change-month">›</div> | |
</div> | |
<div id="date-picker-exit">×</div> | |
<table id="date-picker"> | |
<tr id="date-picker-weekdays"> | |
<th>S</th> | |
<th>M</th> | |
<th>T</th> | |
<th>W</th> | |
<th>T</th> | |
<th>F</th> | |
<th>S</th> | |
</tr> | |
<!-- Actual calendar rows added dynamically --> | |
<!--<tr class="date-picker-calendar-row"></tr>--> | |
</table> | |
</div> | |
</div> | |
</div> | |
[[[https://codepen.io/samwooly/pen/OOKaEz/]]] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var monthFormatter = new Intl.DateTimeFormat("en-us", { month: "long" }); | |
var weekdayFormatter = new Intl.DateTimeFormat("en-us", { weekday: "long" }); | |
var dates = []; | |
dates[0] = new Date(); // defaults to today | |
dates[1] = addDays(dates[0], 31); | |
var currentDate = 0; // index into dates[] | |
var previousDate = 1; | |
var datesBoxes = $(".date-picker-date"); | |
var displayBoxes = $(".date-picker-display"); | |
// sensible default just in case jQuery doesn't kick in | |
// makes sure that the experience is still usable, and when $(window).width() returns then this variable is updated to the correct value | |
var windowWidth = 300; | |
var colourPickerWidth = 300; | |
// set up dates | |
$(document).ready(function() { | |
// will work the same the first time as every other | |
updateDatePicker(); | |
// update dates shown to correct dates | |
$(datesBoxes[0]).text(getDateString(dates[0])); | |
$(datesBoxes[1]).text(getDateString(dates[1])); | |
$(displayBoxes[0]).text(dates[0].getDate() + " " + monthFormatter.format(dates[0]).slice(0,3)); | |
$(displayBoxes[1]).text(dates[1].getDate() + " " + monthFormatter.format(dates[1]).slice(0,3)); | |
}); | |
// add event listeners | |
$(document).ready(function() { | |
// has to be applied each time, as it's removed when calendar is reset | |
applyDateEventListener(); | |
$(".date-picker-date").click(function(e) { | |
// if active, toggle picker off and return | |
var currentlyActive = $(this).hasClass("active"); | |
if (currentlyActive) { | |
$(this).removeClass("active"); | |
hideDatePicker(); | |
return; | |
} | |
$(".date-picker-date").removeClass("active"); | |
$(this).addClass("active"); | |
// update currentDate | |
previousDate = currentDate; | |
if ($(this)[0].id == "date-picker-date-first") { | |
currentDate = 0; | |
} else { | |
currentDate = 1; | |
} | |
// update calendar | |
showDatePicker(e); | |
updateDatePicker(); | |
}); | |
$("#date-picker-next-month").click(function() { | |
changeMonth("Next"); | |
}); | |
$("#date-picker-previous-month").click(function() { | |
changeMonth("Previous"); | |
}); | |
$("#date-picker-exit").click(function() { | |
hideDatePicker(); | |
}); | |
$(document).click(function(e) { | |
var target = $(e.target); | |
var clickedOnPicker = (target.closest("#date-picker-modal").length); | |
var clickedOnDate = (target.closest(".date-picker-date").length); | |
var isPreviousOrNext = target.hasClass("previous-month") || target.hasClass("next-month"); | |
if (!(clickedOnPicker || clickedOnDate || isPreviousOrNext)) { | |
hideDatePicker(); | |
} | |
}); | |
}); | |
// called on initialising (set to today) and then every time the month changes or on moving between dates | |
function updateDatePicker(changeMonth = false) { | |
var datePicker = $("#date-picker"); | |
var curDate = dates[currentDate]; // shorthand | |
// check if it needs to update | |
// updates if changed month directly (changeMonth) or if switched to other .date-picker-date and month is different (differentMonth) | |
var differentMonth = checkChangedMonth(); | |
if (changeMonth === false && differentMonth === false) { return; } | |
updatePickerMonth(); | |
// clear out all tr instances other than the header row | |
// really just removing all rows and appending header row straight back in | |
var headerRow = ` | |
<tr id="date-picker-weekdays"> | |
<th>S</th> | |
<th>M</th> | |
<th>T</th> | |
<th>W</th> | |
<th>T</th> | |
<th>F</th> | |
<th>S</th> | |
</tr>`; | |
// clear all rows | |
datePicker.contents().remove(); | |
datePicker.append(headerRow); | |
var todayDate = curDate.getDate(); | |
var firstOfMonth = new Date(curDate.getFullYear(), curDate.getMonth(), 1); | |
var firstWeekday = firstOfMonth.getDay(); // 0-indexed; 0 is Sunday, 6 is Saturday | |
var lastMonthToInclude = firstWeekday; // happily, this just works as-is. | |
var firstOfNextMonth = addMonths(firstOfMonth, 1); | |
var lastOfMonth = addDays(firstOfNextMonth, -1).getDate(); | |
var openRow = "<tr class='date-picker-calendar-row'>"; | |
var closeRow = "</tr>"; | |
var currentRow = openRow; | |
// Add in as many of last month as required | |
if (lastMonthToInclude > 0) { | |
var lastMonthLastDay = addDays(firstOfMonth, -1); | |
var lastMonthDays = lastMonthLastDay.getDate(); | |
var lastMonthStartAdding = lastMonthDays - lastMonthToInclude + 1; | |
// add days from previous month | |
// takes arguments (start loop, end loop <=, counter, 'true' if current month OR class if another month (optional, default "") ) | |
//addToCalendar(lastMonthStartAdding, lastMonthDays, 0, "previous-month"); | |
//addToCalendar(lastMonthStartAdding, lastMonthDays, 0, "month-previous"); | |
addToCalendar(lastMonthStartAdding, lastMonthDays, 0, "previous-month"); | |
} | |
// fill out rest of row with current month | |
// doesn't matter how many of last month were included, all accounted for | |
addToCalendar(1, 7 - lastMonthToInclude, lastMonthToInclude, true); | |
// reset for current month generation | |
currentRow = openRow; | |
var counter = 7; | |
var addedFromCurrentMonth = 7 - firstWeekday + 1; | |
addToCalendar(addedFromCurrentMonth, lastOfMonth, counter, true); | |
// at this point, counter = all of this month + whatever was included from last month | |
counter = lastMonthToInclude + lastOfMonth; | |
var nextMonthToInclude = counter % 7 === 0 ? 0 : 7 - (counter % 7); | |
addToCalendar(1, nextMonthToInclude, counter, "next-month"); | |
// add event listener again | |
applyDateEventListener(); | |
// update current date box | |
updateDateShown(); | |
// functions scoped to this outer function | |
//############################################################ | |
function checkChangedMonth() { | |
// updates if changed month directly (changeMonth) or if switched to other .date-picker-date and month is different (differentMonth) | |
var differentMonth = false; | |
// checks if it's the same date again | |
if (currentDate !== previousDate) { | |
// if either month or year are different then month has changed | |
if (dates[0].getMonth() !== dates[1].getMonth() || dates[0].getYear() !== dates[1].getYear() ) { | |
differentMonth = true; | |
} | |
} | |
return differentMonth; | |
} | |
function addToCalendar(start, end, counter, cellClass) { | |
var currentMonth = cellClass === true ? true : false; | |
for (var i = start; i <= end; i++) { | |
counter += 1; | |
if (i === todayDate && currentMonth) { | |
currentRow += `<td class="active">${i}</td>`; | |
} else if (cellClass && !currentMonth) { | |
currentRow += `<td class="${cellClass}">${i}</td>`; | |
} else { | |
currentRow += `<td>${i}</td>`; | |
} | |
if (counter % 7 === 0) { | |
datePicker.append(currentRow + closeRow); | |
currentRow = openRow; | |
} | |
} | |
} | |
} | |
function updatePickerMonth() { | |
var monthName = monthFormatter.format(dates[currentDate]); | |
var year = dates[currentDate].getFullYear(); | |
var dateText = monthName + " " + year; | |
$("#date-picker-month").text(dateText); | |
} | |
function dateSelected(currentDay) { | |
// update the active .date-picker-date with the current date | |
var activeDate = $( $(".date-picker-date.active")[0] ); | |
// get current date and update | |
dates[currentDate].setDate(currentDay); | |
updateDateShown(); | |
} | |
// 'direction' can be either "Next" or "Previous" | |
function changeMonth(direction) { | |
var increment = direction === "Next" ? 1 : -1; | |
// change month | |
dates[currentDate] = addMonths(dates[currentDate], increment); | |
// change month name in picker | |
updatePickerMonth(); | |
// update calendar | |
// passes 'true' that month has changed | |
updateDatePicker(true); | |
} | |
function showDatePicker(e) { | |
var pxFromTop = $(".date-picker-date").offset().top; | |
var datePicker = $("#date-picker-modal"); | |
datePicker.css("top", pxFromTop + 40); | |
// check if right edge of colourPicker will go off the edge of the screen, and if so then reduce left by that amount | |
var rightEdge = e.pageX + colourPickerWidth; | |
var overflowWidth = rightEdge - windowWidth; | |
if (overflowWidth > 0) { | |
datePicker.css("left", e.pageX - overflowWidth); | |
} else { | |
datePicker.css("left", e.pageX); | |
} | |
$("#date-picker-modal").removeClass("hidden-2"); | |
} | |
function hideDatePicker() { | |
$(".date-picker-date").removeClass("active"); | |
$("#date-picker-modal").addClass("hidden-2"); | |
} | |
function applyDateEventListener() { | |
$("#date-picker td").click(function() { | |
// Calendar UI | |
$("#date-picker td").removeClass("active"); | |
$(this).addClass("active"); | |
// update variables | |
currentDay = $(this).text(); | |
// update the current date | |
dateSelected(currentDay); | |
// change month based on calendar day class | |
if ($(this).hasClass("previous-month")) { | |
changeMonth("Previous"); | |
} else if ($(this).hasClass("next-month")) { | |
changeMonth("Next"); | |
} else { | |
// clicked in current month; made selection so hide picker again | |
hideDatePicker(); | |
} | |
}); | |
} | |
// Utilities | |
//################################################################ | |
// set location for date picker | |
$(document).ready(function() { | |
updateWidths(); | |
}); | |
$(window).resize(function() { | |
updateWidths(); | |
}); | |
function updateWidths() { | |
windowWidth = $(window).width(); | |
} | |
// courtesy of https://stackoverflow.com/questions/563406/add-days-to-javascript-date | |
function addDays(date, days) { | |
var result = new Date(date); | |
result.setDate(result.getDate() + days); | |
return result; | |
} | |
function addMonths(date, months) { | |
var result = new Date(date); | |
result.setMonth(result.getMonth() + months); | |
return result; | |
} | |
// courtesy of https://stackoverflow.com/a/15764763/7170445 | |
function getDateString(date) { | |
var year = date.getFullYear(); | |
var month = (1 + date.getMonth()).toString(); | |
month = month.length > 1 ? month : '0' + month; | |
var day = date.getDate().toString(); | |
day = day.length > 1 ? day : '0' + day; | |
return day + '/' + month + '/' + year; | |
} | |
function updateDateShown() { | |
var formattedDate = getDateString(dates[currentDate]); | |
var updateDateBox = $(datesBoxes[currentDate]); | |
var updateDisplayBox = $(displayBoxes[currentDate]); | |
var dayAndMonth = dates[currentDate].getDate() + " " + monthFormatter.format(dates[currentDate]).slice(0,3); | |
updateDateBox.text(formattedDate); | |
updateDisplayBox.text(dayAndMonth); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$white: #fff; | |
$lightgray: #F5F5F5; | |
$midlightgray: #aaa; | |
$midgray: #999; | |
$middarkgray: #666; | |
$darkgray: #444; | |
$colour-primary: hsl(157,79%,49%); | |
$colour-primary-light: hsl(157,79%,90%); | |
$font-display: 'Coda', serif; | |
$font-body: 'Open Sans', sans-serif; | |
$drop-shadow-large: 0 0 100px 0 rgba(0,0,0,0.20), 30px 30px 50px 0 rgba(0,0,0,0.06); | |
$drop-shadow-modal: 0 0 40px 0 rgba(0,0,0,0.30), 30px 30px 40px 0 rgba(0,0,0,0.20); | |
$drop-shadow-light: 0 0 20px 0 rgba(0,0,0,0.15), 3px 3px 5px 0 rgba(0,0,0,0.06); | |
$drop-shadow-light-hover: 0 0 25px 0 rgba(0,0,0,0.2), 3px 3px 5px 0 rgba(0,0,0,0.15); | |
// no xs as this is the default styling | |
// these are all used as 'min-width' settings for media queries | |
$bootstrap-sm: 576px; | |
$bootstrap-md: 768px; | |
$bootstrap-lg: 992px; | |
$bootstrap-xl: 1200px; | |
*, *:before, *:after { | |
box-sizing: border-box; | |
transition: all 0.24s; | |
font-size: 16px; | |
font-family: $font-body; | |
border-radius: 5px; | |
} | |
html, body { | |
padding: 0; margin: 0; | |
background: white; | |
} | |
body { | |
background-color: $lightgray; | |
} | |
.hidden { | |
display: none !important; | |
} | |
.hidden-2 { | |
opacity: 0; | |
pointer-events: none; | |
transform: scale(0.92); | |
transform-origin: left center; | |
} | |
#date-picker-section { | |
padding-top: 80px; padding-bottom: 80px; | |
min-height: 80vh; | |
} | |
#date-picker-container { | |
margin: 0 auto; | |
padding: 40px; | |
background: white; | |
max-width: 500px; | |
box-shadow: $drop-shadow-large; | |
} | |
#date-picker-title { | |
font-family: $font-display; | |
margin-bottom: 25px; | |
} | |
#date-picker-dates { | |
background: $lightgray; | |
padding: 10px; | |
margin-bottom: 30px; | |
user-select: none; | |
display: flex; | |
justify-content: space-around; | |
flex-wrap: wrap; | |
} | |
.date-picker-date { | |
position: relative; display: inline-block; | |
background: white; | |
padding: 10px 15px; padding-right: 50px; | |
margin: 10px; | |
cursor: pointer; | |
box-shadow: $drop-shadow-light; | |
transition: none; | |
&:hover, &.active { | |
box-shadow: $drop-shadow-light-hover; | |
} | |
&.active { | |
font-weight: bold; | |
} | |
&:after { | |
position: absolute; display: block; | |
height: 24px; width: 24px; | |
top: 10px; right: 10px; | |
content: ""; | |
background: url("https://image.freepik.com/free-icon/calendar-icon-in-black_318-9776.jpg"); | |
background-repeat: no-repeat; | |
background-size: 24px; | |
} | |
} | |
#date-picker-display-container { | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: space-around; | |
} | |
.date-picker-display-pair { | |
margin: 10px; | |
} | |
.date-picker-display { | |
font-family: $font-display; | |
font-size: 40px; | |
} | |
.date-picker-display-label { | |
text-align: center; | |
font-size: 0.8rem; | |
position: relative; | |
top: -8px; | |
text-transform: uppercase; | |
} | |
#date-picker-modal { | |
width: 310px; | |
box-shadow: $drop-shadow-modal; | |
padding: 15px; | |
position: absolute; | |
overflow: hidden; | |
user-select: none; | |
transition: transform 0.1s; | |
background: white; | |
&:before { | |
position: absolute; display: block; | |
top: 0; left: -5px; right: -5px; | |
height: 96px; | |
background: $colour-primary; | |
content: ''; | |
} | |
} | |
#date-picker-top-bar { | |
position: relative; | |
color: $middarkgray; | |
background: white; | |
width: 195px; | |
margin: 0 auto; margin-bottom: 3px; | |
padding: 5px 0; | |
display: flex; | |
flex-direction: row; | |
justify-content: center; | |
} | |
.date-picker-change-month { | |
font-size: 23px; | |
line-height: 23px; | |
padding: 0 10px; padding-bottom: 4px; | |
cursor: pointer; | |
position: absolute; | |
top: 4px; | |
&:hover { | |
background: $lightgray; | |
} | |
} | |
#date-picker-previous-month { | |
left: 5px; | |
} | |
#date-picker-next-month { | |
right: 5px; | |
} | |
#date-picker-month { | |
margin: 0 20px; | |
} | |
#date-picker-exit { | |
position: absolute; | |
top: 15px; right: 17px; | |
padding: 0 10px; | |
font-size: 23px; | |
cursor: pointer; | |
color: white; | |
&:hover { | |
background: hsla(0,0%,100%,0.3); | |
} | |
} | |
#date-picker { | |
width: 280px; | |
table-layout: fixed; | |
position: relative; | |
th, td { | |
text-align: center; | |
} | |
th { | |
cursor: default; | |
border-radius: 0; | |
height: 50px; | |
border-color: $colour-primary; | |
} | |
td { | |
cursor: pointer; | |
color: #666; | |
border-radius: 50%; | |
height: 38px; | |
&:hover { | |
background: #eee; | |
} | |
&.active { | |
background: $colour-primary; | |
color: white; | |
} | |
&.previous-month, &.next-month { | |
color: #bbb; | |
} | |
} | |
} | |
#date-picker-weekdays { | |
color: white; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css" rel="stylesheet" /> | |
<link href="https://codepen.io/samwooly/pen/OOKaEz/" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment