diff --git a/package.json b/package.json
index 7204c5b0..8ddbd7d9 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
"vitest": "^1.2.2"
},
"dependencies": {
+ "dayjs": "1.11.10",
"pet-dex-utilities": "^1.0.1",
"reset-css": "^5.0.2",
"vite": "^5.0.12",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 909c5d44..59b4bbad 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ dayjs:
+ specifier: 1.11.10
+ version: 1.11.10
pet-dex-utilities:
specifier: ^1.0.1
version: 1.0.1
@@ -4455,6 +4458,10 @@ packages:
es-errors: 1.3.0
is-data-view: 1.0.1
+ /dayjs@1.11.10:
+ resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
+ dev: false
+
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
diff --git a/src/components/Calendar/components/DateButton/index.js b/src/components/Calendar/components/DateButton/index.js
new file mode 100644
index 00000000..e2bae8a5
--- /dev/null
+++ b/src/components/Calendar/components/DateButton/index.js
@@ -0,0 +1,23 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const html = `
+
+`;
+
+export default function DateButton(date) {
+ Component.call(this, { html });
+
+ const $button = this.selected.get('date').children[0];
+ $button.innerText = date;
+}
+
+DateButton.prototype = Object.assign(
+ DateButton.prototype,
+ Component.prototype,
+ {
+ active() {
+ this.selected.get('date').classList.add('active');
+ },
+ },
+);
diff --git a/src/components/Calendar/components/DateButton/index.scss b/src/components/Calendar/components/DateButton/index.scss
new file mode 100644
index 00000000..8c5a228e
--- /dev/null
+++ b/src/components/Calendar/components/DateButton/index.scss
@@ -0,0 +1,63 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.date {
+ button {
+ font-family: fonts.$primaryFont;
+ color: colors.$gray500;
+ font-size: 1.4rem;
+ font-weight: fonts.$medium;
+ line-height: 2rem;
+
+ border: 0;
+
+ background-color: transparent;
+
+ cursor: pointer;
+ }
+}
+
+.date.active {
+ button {
+ color: colors.$primary200;
+ font-size: 2.6rem;
+ font-weight: fonts.$bold;
+ line-height: 3.4rem;
+
+ padding: 0.6rem 1.2rem;
+ border: 0.1rem solid rgb(209, 230, 255);
+
+ background-color: rgba(209, 230, 255, 0.502);
+ border-radius: 1.4rem;
+ }
+}
+
+@include breakpoints.from667 {
+ .date {
+ display: none;
+
+ button {
+ width: 100%;
+
+ font-size: fonts.$md;
+ line-height: 34px;
+ }
+ }
+
+ .date.active {
+ display: flex;
+
+ button {
+ color: colors.$gray800;
+ font-size: 2.6rem;
+ font-weight: fonts.$regular;
+
+ padding: 0.6rem;
+ border: 0;
+
+ background-color: transparent;
+ }
+ }
+}
diff --git a/src/components/Calendar/components/DateSelector/index.js b/src/components/Calendar/components/DateSelector/index.js
new file mode 100644
index 00000000..ad50af7f
--- /dev/null
+++ b/src/components/Calendar/components/DateSelector/index.js
@@ -0,0 +1,219 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+import { listenBreakpoint } from '../../../../utils/breakpoints/breakpoints';
+import DateButton from '../DateButton';
+
+const events = ['changeMonth', 'changeYear'];
+
+const html = `
+
+`;
+
+const monthsBR = [
+ 'Janeiro',
+ 'Fevereiro',
+ 'Março',
+ 'Abril',
+ 'Maio',
+ 'Junho',
+ 'Julho',
+ 'Agosto',
+ 'Setembro',
+ 'Outubro',
+ 'Novembro',
+ 'Dezembro',
+];
+
+let handleTouchStart;
+let handleTouchMove;
+function addTouchEvents(selector) {
+ let startTouch;
+
+ handleTouchStart = (event) => {
+ startTouch = selector.isDesktop
+ ? event.touches[0].clientY
+ : event.touches[0].clientX;
+ };
+
+ handleTouchMove = (event) => {
+ event.preventDefault();
+ const currentTouch = selector.isDesktop
+ ? event.touches[0].clientY
+ : event.touches[0].clientX;
+ let move = 0;
+ const moveRange = 20;
+ move = currentTouch - startTouch;
+
+ if (Math.abs(move) > moveRange) {
+ const isSlideNext = move < 0;
+ const nextYear = +selector.dates[3].children[0].innerText;
+ const prevYear = +selector.dates[1].children[0].innerText;
+ const nextMonth = monthsBR.indexOf(
+ selector.dates[3].children[0].innerText,
+ );
+ const prevMonth = monthsBR.indexOf(
+ selector.dates[1].children[0].innerText,
+ );
+ if (isSlideNext && selector.isYear) selector.setYear(nextYear);
+ if (!isSlideNext && selector.isYear) selector.setYear(prevYear);
+ if (isSlideNext && !selector.isYear) selector.setMonth(nextMonth);
+ if (!isSlideNext && !selector.isYear) selector.setMonth(prevMonth);
+ startTouch = currentTouch;
+ }
+ };
+ selector.dateSelector.addEventListener('touchstart', handleTouchStart);
+ selector.dateSelector.addEventListener('touchmove', handleTouchMove);
+}
+
+let handleScroll;
+function scrollModal(selector) {
+ const $dateSelector = selector.dateSelector;
+
+ handleScroll = (event) => {
+ event.preventDefault();
+ const isScrollNext = event.deltaY > 0;
+
+ if (selector.isYear) {
+ const nextYear = +selector.dates[3].children[0].innerText;
+ const prevYear = +selector.dates[1].children[0].innerText;
+ const newYear = isScrollNext ? nextYear : prevYear;
+ selector.setYear(newYear);
+ } else {
+ const nextMonth = monthsBR.indexOf(
+ selector.dates[3].children[0].innerText,
+ );
+ const prevMonth = monthsBR.indexOf(
+ selector.dates[1].children[0].innerText,
+ );
+ const newMonth = isScrollNext ? nextMonth : prevMonth;
+ selector.setMonth(newMonth);
+ }
+ };
+
+ $dateSelector.addEventListener('wheel', handleScroll);
+ addTouchEvents(selector);
+}
+
+function openModal(selector) {
+ const $dateSelector = selector.dateSelector;
+ $dateSelector.classList.add('active');
+ const $activeDate = selector.date;
+ $activeDate.scrollIntoView({
+ behavior: 'instant',
+ block: 'center',
+ inline: 'center',
+ });
+}
+
+function closeModal(selector, event) {
+ const $dateSelector = selector.dateSelector;
+ if (
+ $dateSelector.classList.contains('active') &&
+ !$dateSelector.contains(event.target)
+ ) {
+ $dateSelector.classList.remove('active');
+ }
+}
+
+export default function DateSelector(dateArray) {
+ Component.call(this, { html, events });
+
+ this.dateSelector = this.selected.get('date-selector');
+ this.dateArray = dateArray;
+
+ this.dateArray.forEach((item, index) => {
+ const dateButton = new DateButton(item);
+ dateButton.mount(this.dateSelector);
+ if (index === 2) {
+ dateButton.active();
+ }
+ });
+
+ this.dates = this.dateSelector.querySelectorAll('li');
+ this.date = this.dateSelector.querySelector('li.active');
+ this.isYear = !Number.isNaN(+this.date.innerText);
+ listenBreakpoint('from667', (matches) => {
+ this.isDesktop = matches;
+ });
+
+ this.dateClickHandle = (index) => () => {
+ if (this.isYear) {
+ this.setYear(this.dateArray[index]);
+ } else {
+ this.setMonth(monthsBR.indexOf(this.dateArray[index]));
+ }
+ };
+
+ this.dates.forEach(($date, index) => {
+ $date.addEventListener('click', this.dateClickHandle(index));
+ });
+ this.openModalHandle = () => openModal(this);
+ this.dateSelector.addEventListener('click', this.openModalHandle);
+
+ this.closeModalHandle = (event) => closeModal(this, event);
+ window.addEventListener('click', this.closeModalHandle);
+
+ scrollModal(this);
+
+ setTimeout(() => {
+ this.date.scrollIntoView({
+ behavior: 'instant',
+ block: 'center',
+ inline: 'center',
+ });
+ });
+
+ this.unmount(() => {
+ this.dateSelector.removeEventListener('click', this.openModalHandle);
+ window.removeEventListener('click', this.closeModalHandle);
+ this.dates.forEach(($date, index) => {
+ $date.removeEventListener('click', this.dateClickHandle(index));
+ });
+ this.dateSelector.removeEventListener('wheel', handleScroll);
+ this.dateSelector.removeEventListener('touchstart', handleTouchStart);
+ this.dateSelector.removeEventListener('touchmove', handleTouchMove);
+ });
+}
+
+DateSelector.prototype = Object.assign(
+ DateSelector.prototype,
+ Component.prototype,
+ {
+ setMonth(newMonth) {
+ for (let i = 0; i < this.dateArray.length; i += 1) {
+ const index = (newMonth - (2 - i) + 12) % 12;
+ this.dateArray[i] = monthsBR[index];
+ }
+
+ this.dateArray.forEach((item, index) => {
+ this.dates[index].children[0].innerText = item;
+ });
+
+ this.date.scrollIntoView({
+ behavior: 'instant',
+ block: 'center',
+ inline: 'center',
+ });
+
+ this.emit('changeMonth', newMonth);
+ },
+
+ setYear(newYear) {
+ for (let i = 0; i < this.dateArray.length; i += 1) {
+ this.dateArray[i] = newYear - (2 - i);
+ }
+
+ this.dateArray.forEach((item, index) => {
+ this.dates[index].children[0].innerText = item;
+ });
+
+ this.date.scrollIntoView({
+ behavior: 'instant',
+ block: 'center',
+ inline: 'center',
+ });
+
+ this.emit('changeYear', newYear);
+ },
+ },
+);
diff --git a/src/components/Calendar/components/DateSelector/index.scss b/src/components/Calendar/components/DateSelector/index.scss
new file mode 100644
index 00000000..5ab3b7d2
--- /dev/null
+++ b/src/components/Calendar/components/DateSelector/index.scss
@@ -0,0 +1,92 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.date-selector {
+ max-width: 33rem;
+ overflow-x: hidden;
+
+ display: flex;
+ gap: 0.8rem;
+
+ align-items: center;
+
+ margin-bottom: 2.4rem;
+ padding-bottom: 2.4rem;
+ border-bottom: 0.1rem solid colors.$gray100;
+
+ &:first-child {
+ order: 2;
+ }
+}
+
+@include breakpoints.from667 {
+ .date-selector {
+ margin-bottom: 0;
+ padding: 0;
+ border-bottom: 0;
+
+ &:last-child {
+ display: flex;
+
+ align-items: center;
+
+ order: 2;
+
+ &::after {
+ width: 1.6rem;
+ height: 2.3rem;
+
+ display: inline;
+
+ background-image: url('../../images/arrows.svg');
+
+ content: '';
+ }
+ }
+ }
+
+ .date-selector.active {
+ width: 23.1rem;
+ max-height: 22.8rem;
+ overflow-y: hidden;
+
+ display: grid;
+ gap: 0.6rem;
+
+ position: absolute;
+ left: -4rem;
+
+ background-color: colors.$secondary100;
+
+ .date {
+ display: block;
+ }
+
+ .date.active {
+ &::after {
+ width: 1.6rem;
+ height: 2.3rem;
+
+ display: inline-block;
+
+ margin-top: 1.4rem;
+
+ position: absolute;
+ right: 2rem;
+
+ background-image: url('../../images/arrows.svg');
+
+ content: '';
+ }
+
+ button {
+ font-weight: fonts.$bold;
+
+ border-top: 0.1rem solid colors.$gray100;
+ border-bottom: 0.1rem solid colors.$gray100;
+ }
+ }
+ }
+}
diff --git a/src/components/Calendar/components/Day/index.js b/src/components/Calendar/components/Day/index.js
new file mode 100644
index 00000000..97e08897
--- /dev/null
+++ b/src/components/Calendar/components/Day/index.js
@@ -0,0 +1,43 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const events = ['changeDay'];
+
+const html = `
+
+`;
+
+export default function Day(day, dayClass) {
+ Component.call(this, { html, events });
+
+ this.$day = this.selected.get('day-button');
+ this.$day.innerText = day;
+ this.$day.setAttribute('aria-label', `Dia ${day}`);
+ if (dayClass) this.setClass(dayClass);
+
+ const setActiveDayHandle = (event) => this.setClassActive(event);
+ this.$day.addEventListener('click', setActiveDayHandle);
+
+ this.unmount(() => {
+ this.$day.removeEventListener('click', setActiveDayHandle);
+ });
+}
+
+Day.prototype = Object.assign(Day.prototype, Component.prototype, {
+ setClass(dayClass) {
+ this.$day.classList.add(dayClass);
+ },
+
+ setClassPrevMonth() {
+ this.$day.classList.add('prev-month');
+ },
+
+ setClassActive() {
+ this.$day.classList.add('active');
+ this.emit('changeDay', this.$day);
+ },
+
+ setClassNextMonth() {
+ this.$day.classList.add('next-month');
+ },
+});
diff --git a/src/components/Calendar/components/Day/index.scss b/src/components/Calendar/components/Day/index.scss
new file mode 100644
index 00000000..987e96fd
--- /dev/null
+++ b/src/components/Calendar/components/Day/index.scss
@@ -0,0 +1,53 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.day-button {
+ width: 4rem;
+ height: 4rem;
+
+ font-family: fonts.$primaryFont;
+ color: colors.$gray500;
+ font-size: 1.6rem;
+ font-weight: fonts.$medium;
+
+ padding: 0;
+ border: 0.1rem solid colors.$gray100;
+
+ background-color: colors.$secondary100;
+ border-radius: 1rem;
+
+ cursor: pointer;
+
+ &.prev-month,
+ &.next-month {
+ color: rgb(160, 174, 192);
+
+ background-color: rgb(247, 250, 252);
+ }
+
+ &:hover,
+ &.active {
+ color: colors.$primary200;
+ font-weight: fonts.$semiBold;
+
+ border: 0.1rem solid rgb(209, 230, 255);
+
+ background-color: rgba(209, 230, 255, 0.502);
+ }
+}
+
+@include breakpoints.from667 {
+ .day-button {
+ width: 5.4rem;
+ height: 5.4rem;
+
+ font-size: 1.8rem;
+
+ &:hover,
+ &.active {
+ border: 0.2rem solid rgb(209, 230, 255);
+ }
+ }
+}
diff --git a/src/components/Calendar/components/Days/index.js b/src/components/Calendar/components/Days/index.js
new file mode 100644
index 00000000..6eda5ae1
--- /dev/null
+++ b/src/components/Calendar/components/Days/index.js
@@ -0,0 +1,54 @@
+import { Component } from 'pet-dex-utilities';
+import Day from '../Day';
+
+const events = ['changeDay', 'changeToPrevMonth', 'changeToNextMonth'];
+
+const html = `
+
+`;
+
+function changeDay(days, newDayActive) {
+ const day = +newDayActive.innerText;
+ const isDayInPrevMonth = newDayActive.classList.contains('prev-month');
+ const isDayInNextMonth = newDayActive.classList.contains('next-month');
+
+ if (isDayInPrevMonth) days.emit('changeToPrevMonth', day);
+ else if (isDayInNextMonth) days.emit('changeToNextMonth', day);
+ else days.emit('changeDay', day);
+}
+
+export default function Days(
+ year,
+ month,
+ day,
+ totalDaysOfMonth,
+ firstDayOfMonthInWeek,
+) {
+ Component.call(this, { html, events });
+
+ const $container = this.selected.get('days-container');
+
+ const totalDaysInCalendar = 42;
+ for (
+ let i = -firstDayOfMonthInWeek;
+ i < totalDaysInCalendar - firstDayOfMonthInWeek;
+ i += 1
+ ) {
+ const date = new Date(year, month, i);
+ const dayNumber = date.getDate();
+ const firstDayOfMonth = 1;
+
+ const dayButton = new Day(dayNumber);
+
+ if (i < firstDayOfMonth) dayButton.setClassPrevMonth();
+ if (i > totalDaysOfMonth) dayButton.setClassNextMonth();
+ if (i === day) dayButton.setClassActive();
+
+ dayButton.mount($container);
+ dayButton.listen('changeDay', (newDayActive) =>
+ changeDay(this, newDayActive),
+ );
+ }
+}
+
+Days.prototype = Object.assign(Days.prototype, Component.prototype, {});
diff --git a/src/components/Calendar/components/NavButton/index.js b/src/components/Calendar/components/NavButton/index.js
new file mode 100644
index 00000000..bef7bfb3
--- /dev/null
+++ b/src/components/Calendar/components/NavButton/index.js
@@ -0,0 +1,36 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+import navButton from '../../images/navButton.svg';
+
+const events = ['prev', 'next'];
+
+const html = `
+
+`;
+
+export default function NavButton(position) {
+ Component.call(this, { html, events });
+
+ this.$navButton = this.selected.get('nav-button');
+ this.$navButton.classList.add(position);
+ const isPrev = this.$navButton.classList.contains('prev');
+ this.$navButton.setAttribute(
+ 'aria-label',
+ isPrev ? 'Ir para o mês anterior' : 'Ir para o próximo mês',
+ );
+
+ const navButtonClickHandle = () => {
+ if (isPrev) {
+ this.emit('prev');
+ } else {
+ this.emit('next');
+ }
+ };
+ this.$navButton.addEventListener('click', navButtonClickHandle);
+}
+
+NavButton.prototype = Object.assign(
+ NavButton.prototype,
+ Component.prototype,
+ {},
+);
diff --git a/src/components/Calendar/components/NavButton/index.scss b/src/components/Calendar/components/NavButton/index.scss
new file mode 100644
index 00000000..455f811e
--- /dev/null
+++ b/src/components/Calendar/components/NavButton/index.scss
@@ -0,0 +1,41 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.nav-button {
+ display: none;
+}
+
+@include breakpoints.from667 {
+ .nav-button {
+ width: 4.6rem;
+ height: 4.6rem;
+
+ display: grid;
+
+ align-items: center;
+ justify-content: center;
+
+ font-family: Nato Sans;
+ color: colors.$gray300;
+ font-size: 2rem;
+
+ border: 0.1rem solid rgb(160, 174, 192);
+
+ background-color: transparent;
+ border-radius: 1.4rem;
+
+ cursor: pointer;
+ }
+
+ .prev {
+ grid-area: 1;
+ }
+
+ .next {
+ justify-self: end;
+
+ transform: rotate(180deg);
+ }
+}
diff --git a/src/components/Calendar/images/arrows.svg b/src/components/Calendar/images/arrows.svg
new file mode 100644
index 00000000..1ebda089
--- /dev/null
+++ b/src/components/Calendar/images/arrows.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/Calendar/images/navButton.svg b/src/components/Calendar/images/navButton.svg
new file mode 100644
index 00000000..ea74a960
--- /dev/null
+++ b/src/components/Calendar/images/navButton.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Calendar/index.js b/src/components/Calendar/index.js
new file mode 100644
index 00000000..87b89c71
--- /dev/null
+++ b/src/components/Calendar/index.js
@@ -0,0 +1,200 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+import dayjs from 'dayjs';
+import DateSelector from './components/DateSelector';
+import NavButton from './components/NavButton';
+import Days from './components/Days';
+
+const events = ['changeDate', 'changeMonth', 'changeYear'];
+
+const monthsBR = [
+ 'Janeiro',
+ 'Fevereiro',
+ 'Março',
+ 'Abril',
+ 'Maio',
+ 'Junho',
+ 'Julho',
+ 'Agosto',
+ 'Setembro',
+ 'Outubro',
+ 'Novembro',
+ 'Dezembro',
+];
+
+const currentMonth = dayjs().month();
+const months = new Array(5);
+for (let i = 0; i < months.length; i += 1) {
+ const monthIndex = (currentMonth - (2 - i) + 12) % 12;
+ months[i] = monthsBR[monthIndex];
+}
+
+const currentYear = dayjs().year();
+const years = new Array(5);
+for (let i = 0; i < years.length; i += 1) {
+ years[i] = currentYear - (2 - i);
+}
+
+const html = `
+
+
+
+
+
Dom
+
Seg
+
Ter
+
Qua
+
Qui
+
Sex
+
Sab
+
+
+`;
+
+export default function Calendar() {
+ Component.call(this, { html, events });
+
+ this.setDate();
+
+ this.monthSelect = new DateSelector(months);
+ this.monthSelect.mount(this.selected.get('calendar-date'));
+ this.monthSelect.listen('changeMonth', (newMonth) =>
+ this.setDate(this.day, newMonth, this.year),
+ );
+
+ this.yearSelect = new DateSelector(years);
+ this.yearSelect.mount(this.selected.get('calendar-date'));
+ this.yearSelect.listen('changeYear', (newYear) =>
+ this.setDate(this.day, this.month, newYear),
+ );
+
+ this.navButton = new NavButton('prev');
+ this.navButton.mount(this.selected.get('calendar-nav'));
+ this.navButton.listen('prev', () => this.goToPrevMonth());
+
+ this.navButton = new NavButton('next');
+ this.navButton.mount(this.selected.get('calendar-nav'));
+ this.navButton.listen('next', () => this.goToNextMonth());
+}
+
+Calendar.prototype = Object.assign(Calendar.prototype, Component.prototype, {
+ getDate() {
+ return {
+ day: this.day,
+ month: this.month,
+ year: this.year,
+ };
+ },
+
+ setDate(day, month, year) {
+ this.day = day || dayjs().date();
+ this.month = month !== undefined ? month : dayjs().month();
+ this.year = year || dayjs().year();
+ this.totalDaysOfMonth = dayjs(
+ `${this.year}-${this.month + 1}`,
+ ).daysInMonth();
+ this.firstDayOfMonthInWeek =
+ dayjs(`${this.year}-${this.month + 1}-1`).day() - 1;
+
+ this.setDays();
+
+ this.emit('changeDate', {
+ day: this.day,
+ month: this.month,
+ year: this.year,
+ });
+ },
+
+ getDay() {
+ return this.day;
+ },
+
+ setDay(day) {
+ if (day < 1 || day > this.totalDaysOfMonth) {
+ throw new Error('Dia inválido');
+ } else if (typeof day !== 'number') {
+ throw new Error('O valor não é um number');
+ } else {
+ this.setDate(day, this.month, this.year);
+ }
+ },
+
+ setDays() {
+ if (this.days) this.days.unmount();
+ this.days = new Days(
+ this.year,
+ this.month,
+ this.day,
+ this.totalDaysOfMonth,
+ this.firstDayOfMonthInWeek,
+ );
+ this.days.mount(this.selected.get('calendar-days'));
+
+ this.days.listen('changeDay', (newDay) => {
+ this.setDate(newDay, this.month, this.year);
+ });
+
+ this.days.listen('changeToPrevMonth', (newDay) => {
+ this.goToPrevMonth();
+ this.setDay(newDay);
+ });
+
+ this.days.listen('changeToNextMonth', (newDay) => {
+ this.goToNextMonth();
+ this.setDay(newDay);
+ });
+ },
+
+ getMonth() {
+ return this.month;
+ },
+
+ setMonth(month) {
+ if (month < 0 || month > 11) {
+ throw new Error('Mês inválido');
+ } else if (typeof month !== 'number') {
+ throw new Error('O valor não é um number');
+ } else {
+ this.setDate(this.day, month, this.year);
+ }
+ },
+
+ getYear() {
+ return this.year;
+ },
+
+ setYear(year) {
+ if (year < 0) {
+ throw new Error('Ano inválido');
+ } else if (typeof year !== 'number') {
+ throw new Error('O valor não é um number');
+ } else {
+ this.setDate(this.day, this.month, year);
+ }
+ },
+
+ goToPrevMonth() {
+ this.month -= 1;
+ if (this.month < 0) {
+ this.month = 11;
+ this.year -= 1;
+ this.yearSelect.setYear(this.year);
+ }
+ this.monthSelect.setMonth(this.month);
+ this.setDate(this.day, this.month, this.year);
+ },
+
+ goToNextMonth() {
+ this.month += 1;
+ if (this.month > 11) {
+ this.month = 0;
+ this.year += 1;
+ this.yearSelect.setYear(this.year);
+ }
+ this.monthSelect.setMonth(this.month);
+ this.setDate(this.day, this.month, this.year);
+ },
+});
diff --git a/src/components/Calendar/index.scss b/src/components/Calendar/index.scss
new file mode 100644
index 00000000..b50dcef6
--- /dev/null
+++ b/src/components/Calendar/index.scss
@@ -0,0 +1,77 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.calendar-container {
+ display: grid;
+
+ padding: 2.4rem 0;
+
+ background-color: rgb(250, 250, 250);
+
+ &__date {
+ width: 100%;
+ max-width: 34rem;
+
+ margin: 0 auto;
+
+ &-select {
+ display: grid;
+
+ justify-items: center;
+ }
+ }
+
+ &__days {
+ margin: 0 auto;
+ padding: 0 1rem;
+
+ div {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 0.8rem;
+
+ justify-items: center;
+ }
+
+ p {
+ font-family: Noto Sans;
+ color: colors.$gray300;
+ text-align: center;
+ font-size: 1.2rem;
+ font-weight: fonts.$regular;
+ line-height: 2rem;
+
+ padding-bottom: 1.2rem;
+ }
+ }
+}
+
+@include breakpoints.from667 {
+ .calendar-container {
+ &__days {
+ p {
+ font-size: 1.6rem;
+ }
+ }
+
+ &__date {
+ max-width: 42rem;
+
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+
+ align-items: center;
+
+ margin: 0 auto;
+ padding-bottom: 3rem;
+
+ &-select {
+ display: flex;
+
+ position: relative;
+ }
+ }
+ }
+}
diff --git a/src/stories/Calendar.stories.js b/src/stories/Calendar.stories.js
new file mode 100644
index 00000000..133cf423
--- /dev/null
+++ b/src/stories/Calendar.stories.js
@@ -0,0 +1,14 @@
+import Calendar from '../components/Calendar';
+
+export default {
+ title: 'Components/Calendar',
+ render: (args) => {
+ const calendar = new Calendar(args);
+ const $container = document.createElement('div');
+ calendar.mount($container);
+
+ return $container;
+ },
+};
+
+export const Default = {};