DigitalConsumer.FixMyGlass/src/common-components/date-picker/date-picker.vue

451 lines
16 KiB
Vue

<template>
<div id="app">
formattedTodayDate is {{ formatDate(todayDateObj) }}
<br />
<span v-if="calendarData.length > 0"
>calendarData[0].monthLabel is {{ calendarData[0].monthLabel }}</span
>
<br />
<fieldset v-if="calendarData">
<legend class="sr-only">Date Picker</legend>
<span
v-for="month in calendarData"
:key="`${month.monthLabel}-${month.yearNum.toString()}`">
<div class="calendar-grid-container">
<div class="month-year body-small d-flex align-items-center ps-3 small">
{{ month.monthLabel }} {{ month.yearNum.toString() }}
</div>
<div class="legend caption d-flex align-items-center justify-content-end">
<span class="legend-circle me-1"></span> &equals; Available
</div>
<div class="nav-back ps-3"><button></button></div>
<div class="nav-forward pe-3"><button></button></div>
<!-- Do the days of the weeek need to be read? -->
<div class="grid-item caption"><span class="sr-only">Sunday</span>S</div>
<div class="grid-item caption"><span class="sr-only">Monday</span>M</div>
<div class="grid-item caption"><span class="sr-only">Tuesday</span>T</div>
<div class="grid-item caption"><span class="sr-only">Wednesday</span>W</div>
<div class="grid-item caption"><span class="sr-only">Thursday</span>T</div>
<div class="grid-item caption"><span class="sr-only">Friday</span>F</div>
<div class="grid-item caption"><span class="sr-only">Saturday</span>S</div>
<div
class="grid-item radio-wrapper"
:class="[date.dateNum === 1 ? 'first-day-' + month.startDayIndex : '']"
v-for="date in month.dates"
:key="`${month.monthLabel}-${date.dateNum.toString()}`">
<!-- TODO: maybe make date.dateNum a string in the data. Any reason not to? -->
<input
disabled
type="radio"
name="day-of-month"
id="`${month.monthLabel}-${date.dateNum.toString()}`" />
<label for="`${month.monthLabel}-${date.dateNum.toString()}`"
><span>{{ date.dateNum.toString() }}</span></label
>
</div>
</div>
</span>
</fieldset>
</div>
</template>
<script>
export default {
name: "datePicker",
data() {
return {
currentMonth: "February",
currentYear: "2023",
isFirstDayOfMonth: true,
isCurrentDay: true,
// calendarData: [],
monthsBeforeToLoadOffset: -1,
monthsAfterToLoadOffset: 1,
};
},
props: {
todayOverrideDateString: { // only used for unit tests to override today's date
type: String,
default: null,
},
},
computed: {
todayDateObj() {
return this.todayOverrideDateString ? new Date(this.todayOverrideDateString) : new Date();
},
calendarData() {
return this.setCalendarData();
},
// initialFirstDate() {
// return startOfWeek(this.todayDateObj);
// },
},
// mounted() {
// this.setCalendarData();
// },
methods: {
formatDate(date) {
// return format(date, "MM/dd/yyyy");
// console.log("date: ", date)
const dateArr = date.toDateString().split(" ");
// console.log("dateArr: ", dateArr)
const dayIndex = date.getDay();
// console.log("dayIndex: ", dayIndex)
const dateIndex = date.getDate();
// console.log("dateIndex: ", dateIndex)
const yearIndex = date.getFullYear();
// console.log("yearIndex: ", yearIndex)
const monthIndex = date.getMonth();
// console.log("monthIndex: ", monthIndex)
const monthIndexStr = this.formatNumberToSetDigits(monthIndex + 1, 2);
const dateIndexStr = this.formatNumberToSetDigits(dateIndex, 2);
return monthIndexStr + "/" + dateIndexStr + "/" + yearIndex.toString();
},
formatNumberToSetDigits(num, digits) {
let numStr = num.toString();
// const strLength = numStr.length;
while (numStr.length < digits) {
numStr = "0" + numStr;
}
return numStr;
},
setCalendarData() {
const monthsOfYear = [ // TODO: MOVE THIS TO A CONSTANT
"January", // 0 // -12 // 12
"February", // 1 // -11 // 13
"March", // 2 // -10 // 14
"April", // 3 // -9 // 15
"May", // 4 // -8 // 16
"June", // 5 // -7 // 17
"July", // 6 // -6 // 18
"August", // 7 // -5 // 19
"September", // 8 // -4 // 20
"October", // 9 // -3 // 21
"November", // 10 // -2 // 22
"December", // 11 // -1 // 23
];
const todayDateObj = this.todayDateObj;
const todayMonth = todayDateObj.getMonth(); // returns zero index of months
const todayYear = todayDateObj.getFullYear(); // returns # of year
// const todayDate = todayDateObj.getDate(); // returns # of date
// const todayMonthFirstDate = new Date(todayYear, todayMonth, 1); // returns date object
// const todayMonthLastDate = new Date(todayYear, todayMonth + 1, 0); // returns date object
// const todayMonthLastDateNum = todayMonthLastDate.getDate(); // returns # of date
// const todayStartDayIndex = todayMonthFirstDate.getDay(); // returns zero index of weekdays
function getMonthData(offset) {
let monthClass = "";
let yearNum = todayYear;
let adjustedMonthIndex = todayMonth + offset;
// console.log("111... todayMonth: ", todayMonth, " / yearNum: ", yearNum, " / offset: ", offset);
if (offset === 0) {
monthClass = "currentMonth";
} else if (offset > 0) {
monthClass = "futureMonth";
while (adjustedMonthIndex > 11) {
adjustedMonthIndex = adjustedMonthIndex - 12;
yearNum++;
}
} else {
monthClass = "pastMonth";
while (adjustedMonthIndex < 0) {
adjustedMonthIndex = 12 + adjustedMonthIndex;
yearNum--;
}
}
// console.log("adjustedMonthIndex: ", adjustedMonthIndex, " / yearNum: ", yearNum);
const monthEndDate = new Date(yearNum, adjustedMonthIndex + 1, 0);
const datesArray = [];
for (let i = 1; i <= monthEndDate.getDate(); i++) {
datesArray.push({ dateNum: i });
}
const monthFirstDate = new Date(yearNum, adjustedMonthIndex, 1); // returns date object
const startDayIndex = monthFirstDate.getDay();
console.log("startDayIndex: ", startDayIndex)
const monthToAdd = {
monthLabel: monthsOfYear[adjustedMonthIndex],
yearNum: yearNum,
dates: datesArray,
startDayIndex: startDayIndex,
monthClass: monthClass,
};
// console.log("monthToAdd: ", monthToAdd)
return monthToAdd;
}
// this.monthsBeforeToLoadOffset, this.monthsAfterToLoadOffset (DATA VARS)
const months = [];
for (let i = this.monthsBeforeToLoadOffset; i <= this.monthsAfterToLoadOffset; i++) {
months.push(getMonthData(i));
}
return months;
},
getTodayDate() {
const todayDate = new Date();
return todayDate;
},
},
};
</script>
<style lang="scss" scoped>
hr {
margin: 90px;
}
.calendar-grid-container {
margin: 0 auto;
max-width: 414px;
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-content: center;
align-items: center;
padding: 4px;
.grid-item {
text-align: center;
margin: 10%;
&.first-day-,
&.first-day-0 {
grid-column-start: 1;
}
&.first-day-1 {
grid-column-start: 2;
}
&.first-day-2 {
grid-column-start: 3;
}
&.first-day-3 {
grid-column-start: 4;
}
&.first-day-4 {
grid-column-start: 5;
}
&.first-day-5 {
grid-column-start: 6;
}
&.first-day-6 {
grid-column-start: 7;
}
}
.month-year {
grid-area: 1 / 1 / 2 / 4;
text-transform: uppercase;
font-weight: 300;
letter-spacing: 0.75px;
}
.legend {
grid-area: 1 / 4 / 2 / 6;
.legend-circle {
border-radius: 50%;
width: 16px;
height: 16px;
background-color: $blue-100;
border: 1px solid $blue;
}
}
.nav-forward {
grid-area: 1 / 7 / 2 / 8;
display: flex;
justify-content: flex-end;
button {
&:after {
content: "";
position: absolute;
width: 7px;
height: 12px;
background-image: url("data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.66831 5.99879C6.66955 6.17554 6.60074 6.34558 6.47695 6.47168L1.15501 11.8017C1.02812 11.9287 0.85603 12 0.676587 12C0.497145 12 0.325053 11.9287 0.198168 11.8017C0.0712831 11.6748 7.268e-09 11.5026 8.07183e-09 11.3231C8.87567e-09 11.1436 0.0712831 10.9714 0.198168 10.8445L5.05126 5.99879L0.198168 1.14736C0.0725521 1.02042 0.00248585 0.848755 0.00338306 0.670131C0.00428028 0.491506 0.0760674 0.320554 0.202952 0.194881C0.329837 0.0692091 0.501426 -0.000888818 0.679971 9.54485e-06C0.858515 0.000906955 1.02939 0.0727263 1.15501 0.199668L6.47312 5.52399C6.59843 5.65017 6.66862 5.82092 6.66831 5.99879Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
}
}
}
.nav-back {
grid-area: 1 / 6 / 2 / 7;
display: flex;
justify-content: flex-end;
button {
&:after {
content: "";
position: absolute;
width: 7px;
height: 12px;
background-image: url("data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.331685 6.00121C0.330445 5.82446 0.399256 5.65442 0.523053 5.52832L5.84499 0.198256C5.97188 0.0713149 6.14397 -8.63821e-08 6.32341 -6.72174e-08C6.50285 -4.80527e-08 6.67495 0.071315 6.80183 0.198256C6.92872 0.325198 7 0.497368 7 0.67689C7 0.856412 6.92872 1.02858 6.80183 1.15552L1.94874 6.00121L6.80183 10.8526C6.92745 10.9796 6.99751 11.1512 6.99662 11.3299C6.99572 11.5085 6.92393 11.6794 6.79705 11.8051C6.67016 11.9308 6.49857 12.0009 6.32003 12C6.14148 11.9991 5.97061 11.9273 5.84499 11.8003L0.526881 6.47601C0.401568 6.34983 0.331375 6.17908 0.331685 6.00121Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
}
}
}
.nav-back,
.nav-forward {
button {
position: relative;
border-radius: 50%;
width: 28px;
height: 28px;
background-color: $blue-100;
border: 1px solid $blue;
display: flex;
justify-content: center;
align-items: center;
&:focus,
&:focus-visible {
border: 2.5px solid $blue;
box-shadow: none;
background-color: $blue-100;
color: $white;
}
}
}
.radio-wrapper {
position: relative;
display: flex;
justify-content: center;
align-items: center;
outline: none;
input[type="radio"] {
position: absolute; //override bootstrap
height: 0;
opacity: 0;
&:focus-visible + label {
box-shadow: 0 0 0 2.5px $blue;
}
&:focus + label,
&:checked:focus + label {
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1;
background-color: $blue;
color: $white;
&.past-day {
box-shadow: none;
background-color: transparent;
color: $gray-500;
font-weight: normal;
}
&.current-day {
&:after {
background-color: $white;
}
.first-day {
color: $white;
}
}
}
&:checked + label {
color: $white;
background: $blue;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue;
&:after {
background-color: $white;
}
.first-day {
color: $white;
}
}
}
.first-day {
position: absolute;
font-size: 9px;
font-weight: 500;
color: $gray-600;
top: 0;
z-index: 1;
}
label {
position: relative;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
min-width: 40px;
border-radius: 50%;
&.past-day {
color: $gray-500;
background-color: transparent;
border: 1px solid transparent;
pointer-events: none;
}
&.selectable-day {
color: $blue;
background-color: $blue-100;
border: 1px solid $blue;
}
&.current-day {
&:after {
content: "";
width: 0.25rem;
height: 0.25rem;
border-radius: 50%;
background-color: $blue;
position: absolute;
top: 28px;
}
.first-day {
color: $blue;
}
}
&.future-day {
color: $gray-600;
background-color: transparent;
border: 1px solid transparent;
}
span {
&.small {
font-size: 0.75rem;
color: $gray-550;
}
}
&:hover,
&:checked {
@include media-breakpoint-up(sm) {
box-shadow: 0 0 0 4px transparent;
background-color: $blue;
color: $white;
&:after {
background-color: $white;
}
}
cursor: pointer;
&.current-day {
+ .first-day {
color: $white;
}
}
.first-day {
color: $white;
}
}
+ p {
display: none;
}
}
}
}
</style>