diff --git a/src/components/Button/index.scss b/src/components/Button/index.scss
index 61d804ba..1d15d6c8 100644
--- a/src/components/Button/index.scss
+++ b/src/components/Button/index.scss
@@ -1,36 +1,36 @@
-@use '~styles/colors.scss' as colors;
-
-.button {
- font-family: 'Noto Sans', sans-serif;
- color: rgb(255, 255, 255);
- font-size: 1.6rem;
- font-weight: 500;
-
- padding: 1.6rem;
- border: unset;
-
- background-color: colors.$primary200;
- border-radius: 1.4rem;
-
- transition: 0.3s ease-in-out;
-
- cursor: pointer;
- appearance: none;
-
- &--block {
- width: 100%;
-
- display: block;
- }
-
- &:hover:not(:disabled) {
- background: colors.$primary600;
- transform: scale(1.02);
- }
-
- &:disabled {
- background: colors.$gray600;
-
- cursor: not-allowed;
- }
-}
+@use '~styles/colors.scss' as colors;
+
+.button {
+ font-family: 'Noto Sans', sans-serif;
+ color: colors.$secondary100;
+ font-size: 1.6rem;
+ font-weight: 500;
+
+ padding: 1.6rem;
+ border: unset;
+
+ background-color: colors.$primary200;
+ border-radius: 1.4rem;
+
+ transition: 0.3s ease-in-out;
+
+ cursor: pointer;
+ appearance: none;
+
+ &--block {
+ width: 100%;
+
+ display: block;
+ }
+
+ &:hover:not(:disabled) {
+ background: colors.$primary600;
+ transform: scale(1.02);
+ }
+
+ &:disabled {
+ background: colors.$gray600;
+
+ cursor: not-allowed;
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/DateSelected/index.js b/src/components/Calendar/components/DateSelectorComposer/components/DateSelected/index.js
new file mode 100644
index 00000000..3010d63e
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/DateSelected/index.js
@@ -0,0 +1,32 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const events = ['item:click'];
+
+const html = `
+
+`;
+
+export default function DateSelected({ date = 1 }) {
+ Component.call(this, { html, events });
+
+ this.$dateSelected = this.selected.get('date-selected');
+ this.setDate(date);
+
+ this.$dateSelected.addEventListener('click', () => this.emit('item:click'));
+}
+
+DateSelected.prototype = Object.assign(
+ DateSelected.prototype,
+ Component.prototype,
+ {
+ setDate(date) {
+ this.date = date;
+ this.$dateSelected.innerText = date;
+ },
+
+ getDate() {
+ return this.date;
+ },
+ },
+);
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/DateSelected/index.scss b/src/components/Calendar/components/DateSelectorComposer/components/DateSelected/index.scss
new file mode 100644
index 00000000..74871321
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/DateSelected/index.scss
@@ -0,0 +1,16 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.date-selected {
+ font-family: fonts.$primaryFont;
+ color: colors.$gray800;
+ font-size: fonts.$lg;
+ font-weight: fonts.$regular;
+
+ &:hover {
+ color: colors.$primary200;
+ font-weight: fonts.$semiBold;
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.js b/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.js
new file mode 100644
index 00000000..507438f4
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.js
@@ -0,0 +1,146 @@
+import { Component } from 'pet-dex-utilities';
+import { makeSwipable } from '~src/utils/swiper';
+import SelectorItem from '../SelectorItem';
+import { MONTHS } from '../../../../utils/months';
+import './index.scss';
+
+const events = ['month:change', 'selector:click'];
+
+const html = `
+
+`;
+
+export default function MonthSelector({ dateArray, nodePadding = 5 }) {
+ Component.call(this, { html, events });
+
+ this.dateArray = dateArray;
+ this.$monthSelector = this.selected.get('month-selector');
+ this.$dateList = this.selected.get('month-list');
+ this.$listContent = this.selected.get('list-content');
+
+ this.itemCount = this.dateArray.length;
+ const columnWidth = 150;
+ const activeColumnWidth = 176;
+ this.nodePadding = nodePadding;
+ this.scrollLeft = this.$monthSelector.scrollLeft;
+
+ const swiper = makeSwipable(this.$monthSelector);
+
+ const handleItemClick = (index) => {
+ const itemScroll =
+ index * columnWidth - (this.viewportWidth / 2 - columnWidth / 2);
+ this.$monthSelector.scrollLeft = itemScroll;
+ };
+
+ const emitMonthChangeEvent = (month) => {
+ const newMonth = MONTHS.indexOf(month);
+ this.emit('month:change', newMonth);
+ };
+
+ const calculateViewport = () => {
+ this.viewportWidth = this.$monthSelector.offsetWidth;
+ };
+
+ const renderWindow = () => {
+ this.totalContentWidth =
+ (this.itemCount - 1) * columnWidth + activeColumnWidth;
+
+ this.startNode =
+ Math.floor(this.scrollLeft / columnWidth) - this.nodePadding;
+ this.startNode = Math.max(0, this.startNode);
+
+ this.visibleNodesCount =
+ Math.ceil(this.viewportWidth / columnWidth) + 2 * this.nodePadding;
+ this.visibleNodesCount = Math.min(
+ this.itemCount - this.startNode,
+ this.visibleNodesCount,
+ );
+
+ this.offsetX = this.startNode * columnWidth;
+
+ this.$dateList.style.width = `${this.totalContentWidth}px`;
+ this.$listContent.style.transform = `translateX(${this.offsetX}px)`;
+
+ this.$listContent.innerHTML = '';
+ this.visibleChildren = new Array(this.visibleNodesCount)
+ .fill(null)
+ .map(
+ (_, index) =>
+ new SelectorItem({ item: this.dateArray[index + this.startNode] }),
+ );
+
+ this.visibleChildren.forEach((selectorItem, index) => {
+ selectorItem.mount(this.$listContent);
+ selectorItem.listen('item:click', () =>
+ handleItemClick(index + this.startNode),
+ );
+ selectorItem.listen('item:change', (item) => emitMonthChangeEvent(item));
+ });
+
+ const middlePosition = this.scrollLeft + this.viewportWidth / 2;
+ const activeIndex = Math.round(
+ (middlePosition - this.offsetX) / columnWidth - 1,
+ );
+
+ if (activeIndex >= 0 && activeIndex < this.visibleChildren.length) {
+ this.visibleChildren[activeIndex].active();
+ }
+ };
+
+ const scrollToMiddle = () => {
+ this.scrollLeft = this.totalContentWidth / 2 - this.viewportWidth / 2;
+ this.$monthSelector.scrollLeft = this.scrollLeft;
+ renderWindow();
+ };
+
+ this.$monthSelector.addEventListener('scroll', (e) => {
+ if (this.animationFrame) {
+ cancelAnimationFrame(this.animationFrame);
+ }
+ this.animationFrame = requestAnimationFrame(() => {
+ this.scrollLeft = e.target.scrollLeft;
+ renderWindow();
+ });
+ });
+
+ swiper.left(() => {
+ this.scrollLeft = Math.max(this.scrollLeft - columnWidth, 0);
+ this.$monthSelector.scrollLeft = this.scrollLeft;
+ renderWindow();
+ });
+
+ swiper.right(() => {
+ this.scrollLeft = Math.min(
+ this.scrollLeft + columnWidth,
+ this.totalContentWidth - this.viewportWidth,
+ );
+ this.$monthSelector.scrollLeft = this.scrollLeft;
+ renderWindow();
+ });
+
+ this.listen('mount', () => {
+ window.addEventListener('resize', () => {
+ calculateViewport();
+ scrollToMiddle();
+ });
+ });
+
+ requestAnimationFrame(() => {
+ calculateViewport();
+ renderWindow();
+ scrollToMiddle();
+ });
+}
+
+MonthSelector.prototype = Object.assign(
+ MonthSelector.prototype,
+ Component.prototype,
+ {},
+);
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.scss b/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.scss
new file mode 100644
index 00000000..4d5773d5
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.scss
@@ -0,0 +1,31 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.month-selector {
+ overflow: auto;
+
+ display: flex;
+ flex-direction: row;
+
+ margin-bottom: 2.4rem;
+
+ padding-bottom: 2.4rem;
+
+ border-bottom: 0.1rem solid colors.$gray150;
+
+ box-sizing: border-box;
+
+ scroll-snap-type: x proximity;
+
+ scrollbar-width: none;
+
+ &__list {
+ width: fit-content;
+
+ display: flex;
+
+ align-items: center;
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.js b/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.js
new file mode 100644
index 00000000..fd568bd4
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.js
@@ -0,0 +1,32 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const events = ['item:change', 'item:click'];
+
+const html = `
+
+`;
+
+export default function SelectorItem({ item }) {
+ Component.call(this, { html, events });
+ this.item = item;
+ this.$selectorItem = this.selected.get('selector-item');
+ this.$selectorItem.innerText = this.item;
+
+ if (typeof this.item === 'string') this.$selectorItem.style.width = '150px';
+
+ if (typeof this.item === 'number') this.$selectorItem.style.width = '70px';
+
+ this.$selectorItem.addEventListener('click', () => this.emit('item:click'));
+}
+
+SelectorItem.prototype = Object.assign(
+ SelectorItem.prototype,
+ Component.prototype,
+ {
+ active() {
+ this.$selectorItem.classList.add('selector-item--active');
+ this.emit('item:change', this.item);
+ },
+ },
+);
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.scss b/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.scss
new file mode 100644
index 00000000..343921ac
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.scss
@@ -0,0 +1,27 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.selector-item {
+ font-family: fonts.$primaryFont;
+ color: colors.$gray700;
+
+ text-align: center;
+ font-size: fonts.$xs;
+ font-weight: fonts.$medium;
+
+ scroll-snap-align: center;
+
+ &--active {
+ color: colors.$primary200;
+ font-size: fonts.$lg;
+ font-weight: fonts.$semiBold;
+
+ padding: 0.6rem 1.2rem;
+ border: 1px solid colors.$blue100;
+
+ background-color: rgba(209, 230, 255, 0.5);
+ border-radius: 1.4rem;
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/index.js b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/index.js
new file mode 100644
index 00000000..6059f234
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/index.js
@@ -0,0 +1,27 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const events = ['item:change', 'item:click'];
+
+const html = `
+
+`;
+
+export default function ModalItem({ item }) {
+ Component.call(this, { html, events });
+
+ this.item = item;
+ this.$modalItem = this.selected.get('modal-item');
+ this.$modalItem.innerText = this.item;
+
+ this.$modalItem.addEventListener('click', () =>
+ this.emit('item:click', this.item),
+ );
+}
+
+ModalItem.prototype = Object.assign(ModalItem.prototype, Component.prototype, {
+ active() {
+ this.$modalItem.classList.add('selector-modal__item--active');
+ this.emit('item:change', this.item);
+ },
+});
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/index.scss b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/index.scss
new file mode 100644
index 00000000..8a2d260f
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/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;
+
+.selector-modal__item {
+ font-family: fonts.$primaryFont;
+ color: colors.$gray500;
+ text-align: center;
+ font-size: fonts.$md;
+ font-weight: fonts.$medium;
+ line-height: 1.7;
+
+ cursor: pointer;
+
+ &--active {
+ color: colors.$gray800;
+ font-size: fonts.$lg;
+ font-weight: fonts.$bold;
+
+ border-top: 1px solid colors.$gray150;
+ border-bottom: 1px solid colors.$gray150;
+
+ position: relative;
+
+ &::after {
+ width: 1.6rem;
+ height: 2.3rem;
+
+ margin-left: 1rem;
+
+ position: absolute;
+
+ top: 0.9rem;
+
+ background-image: url('../../../../../../images/arrows.svg');
+
+ content: '';
+ }
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.js b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.js
new file mode 100644
index 00000000..19733374
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.js
@@ -0,0 +1,109 @@
+import { Component } from 'pet-dex-utilities';
+import ModalItem from './components/ModalItem';
+
+import './index.scss';
+
+const events = ['date:change'];
+
+const html = `
+
+`;
+
+export default function SelectorModal({ dateArray, nodePadding = 5 }) {
+ Component.call(this, { html, events });
+
+ this.dateArray = dateArray;
+ this.$selectorModal = this.selected.get('selector-modal');
+ this.$modalList = this.selected.get('modal-list');
+ this.$listContent = this.selected.get('list-content');
+
+ this.itemCount = this.dateArray.length;
+ const rowHeight = 34;
+ const activeRowHeight = 42.8;
+ this.nodePadding = nodePadding;
+ this.scrollTop = this.$selectorModal.scrollTop;
+
+ setTimeout(() => {
+ this.viewportHeight = this.$selectorModal.offsetHeight;
+
+ const renderWindow = () => {
+ this.totalContentHeight =
+ (this.itemCount - 1) * rowHeight + activeRowHeight;
+
+ this.startNode =
+ Math.floor(this.scrollTop / rowHeight) - this.nodePadding;
+ this.startNode = Math.max(0, this.startNode);
+
+ this.visibleNodesCount =
+ Math.ceil(this.viewportHeight / rowHeight) + 2 * this.nodePadding;
+ this.visibleNodesCount = Math.min(
+ this.itemCount - this.startNode,
+ this.visibleNodesCount,
+ );
+
+ this.offsetY = this.startNode * rowHeight;
+
+ this.$modalList.style.height = `${this.totalContentHeight}px`;
+ this.$listContent.style.transform = `translateY(${this.offsetY}px)`;
+
+ this.$listContent.innerHTML = '';
+ this.visibleChildren = new Array(this.visibleNodesCount)
+ .fill(null)
+ .map(
+ (_, index) =>
+ new ModalItem({ item: this.dateArray[index + this.startNode] }),
+ );
+
+ this.visibleChildren.forEach((modalItem, index) => {
+ modalItem.mount(this.$listContent);
+ modalItem.listen('item:change', (item) =>
+ this.emit('date:change', item),
+ );
+
+ if (index === 8) {
+ modalItem.active();
+ }
+ });
+ };
+ this.$selectorModal.addEventListener('scroll', (e) => {
+ if (this.animationFrame) {
+ cancelAnimationFrame(this.animationFrame);
+ }
+ this.animationFrame = requestAnimationFrame(() => {
+ this.scrollTop = e.target.scrollTop;
+ renderWindow();
+
+ const activeItem = this.$listContent.querySelector(
+ 'selector-item--active',
+ );
+ if (activeItem) {
+ activeItem.scrollIntoView({
+ block: 'center',
+ behavior: 'smooth',
+ });
+ }
+ });
+ });
+
+ const scrollToMiddle = () => {
+ this.scrollTop = this.totalContentHeight / 2 - this.viewportHeight / 2;
+ this.$selectorModal.scrollTop = this.scrollTop;
+ };
+
+ renderWindow();
+ scrollToMiddle();
+ }, 0);
+}
+
+SelectorModal.prototype = Object.assign(
+ SelectorModal.prototype,
+ Component.prototype,
+ {},
+);
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.scss b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.scss
new file mode 100644
index 00000000..eac998b2
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.scss
@@ -0,0 +1,54 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.selector-modal {
+ width: 100%;
+ height: 18rem;
+ overflow: auto;
+ scroll-snap-type: y proximity;
+
+ display: none;
+ flex-direction: column;
+
+ box-sizing: border-box;
+
+ position: absolute;
+ top: 4.6rem;
+ z-index: 2;
+
+ background-color: colors.$secondary100;
+
+ box-shadow: 0 4px 20px -2px rgba(50, 50, 71, 0.04);
+
+ box-shadow: 0 0 5px 0 rgba(12, 26, 75, 0.08);
+
+ border-radius: 1rem;
+
+ animation: openModal 0.3s ease-out;
+
+ scrollbar-width: none;
+
+ &__list > * {
+ scroll-snap-align: center;
+ }
+}
+
+@include breakpoints.from667 {
+ .selector-modal {
+ display: flex;
+ }
+}
+
+@keyframes openModal {
+ from {
+ transform: translateY(-2.5rem);
+ opacity: 0;
+ }
+
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.js b/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.js
new file mode 100644
index 00000000..7214386f
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.js
@@ -0,0 +1,142 @@
+import { Component } from 'pet-dex-utilities';
+import { makeSwipable } from '~src/utils/swiper';
+import SelectorItem from '../SelectorItem';
+import './index.scss';
+
+const events = ['selector:click', 'year:change'];
+
+const html = `
+
+`;
+
+export default function YearSelector({ dateArray, nodePadding = 5 }) {
+ Component.call(this, { html, events });
+
+ this.dateArray = dateArray;
+ this.$yearSelector = this.selected.get('year-selector');
+ this.$dateList = this.selected.get('date-list');
+ this.$listContent = this.selected.get('list-content');
+
+ this.itemCount = this.dateArray.length;
+ const columnWidth = 70;
+ const activeColumnWidth = 96;
+ this.nodePadding = nodePadding;
+ this.scrollLeft = this.$yearSelector.scrollLeft;
+
+ const swiper = makeSwipable(this.$yearSelector);
+
+ const handleItemClick = (index) => {
+ const itemScroll =
+ index * columnWidth - (this.viewportWidth / 2 - columnWidth / 2);
+ this.$yearSelector.scrollLeft = itemScroll;
+ };
+
+ const calculateViewport = () => {
+ this.viewportWidth = this.$yearSelector.offsetWidth;
+ };
+
+ const renderWindow = () => {
+ this.totalContentWidth =
+ (this.itemCount - 1) * columnWidth + activeColumnWidth;
+
+ this.startNode =
+ Math.floor(this.scrollLeft / columnWidth) - this.nodePadding;
+ this.startNode = Math.max(0, this.startNode);
+
+ this.visibleNodesCount =
+ Math.ceil(this.viewportWidth / columnWidth) + 2 * this.nodePadding;
+ this.visibleNodesCount = Math.min(
+ this.itemCount - this.startNode,
+ this.visibleNodesCount,
+ );
+
+ this.offsetX = this.startNode * columnWidth;
+
+ this.$dateList.style.width = `${this.totalContentWidth}px`;
+ this.$listContent.style.transform = `translateX(${this.offsetX}px)`;
+
+ this.$listContent.innerHTML = '';
+ this.visibleChildren = new Array(this.visibleNodesCount)
+ .fill(null)
+ .map(
+ (_, index) =>
+ new SelectorItem({ item: this.dateArray[index + this.startNode] }),
+ );
+
+ this.visibleChildren.forEach((selectorItem, index) => {
+ selectorItem.mount(this.$listContent);
+ selectorItem.listen('item:click', () =>
+ handleItemClick(index + this.startNode),
+ );
+ selectorItem.listen('item:change', (item) =>
+ this.emit('year:change', item),
+ );
+ });
+
+ const middlePosition = this.scrollLeft + this.viewportWidth / 2;
+ const activeIndex = Math.round(
+ (middlePosition - this.offsetX) / columnWidth - 1,
+ );
+
+ if (activeIndex >= 0 && activeIndex < this.visibleChildren.length) {
+ this.visibleChildren[activeIndex].active();
+ }
+ };
+
+ const scrollToMiddle = () => {
+ this.scrollLeft = this.totalContentWidth / 2 - this.viewportWidth / 2;
+ this.$yearSelector.scrollLeft = this.scrollLeft;
+ renderWindow();
+ };
+
+ this.$yearSelector.addEventListener('scroll', (e) => {
+ if (this.animationFrame) {
+ cancelAnimationFrame(this.animationFrame);
+ }
+ this.animationFrame = requestAnimationFrame(() => {
+ this.scrollLeft = e.target.scrollLeft;
+ renderWindow();
+ });
+ });
+
+ swiper.left(() => {
+ this.scrollLeft = Math.max(this.scrollLeft - columnWidth, 0);
+ this.$yearSelector.scrollLeft = this.scrollLeft;
+ renderWindow();
+ });
+
+ swiper.right(() => {
+ this.scrollLeft = Math.min(
+ this.scrollLeft + columnWidth,
+ this.totalContentWidth - this.viewportWidth,
+ );
+ this.$yearSelector.scrollLeft = this.scrollLeft;
+ renderWindow();
+ });
+
+ this.listen('mount', () => {
+ window.addEventListener('resize', () => {
+ calculateViewport();
+ scrollToMiddle();
+ });
+ });
+
+ requestAnimationFrame(() => {
+ calculateViewport();
+ renderWindow();
+ scrollToMiddle();
+ });
+}
+
+YearSelector.prototype = Object.assign(
+ YearSelector.prototype,
+ Component.prototype,
+ {},
+);
diff --git a/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.scss b/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.scss
new file mode 100644
index 00000000..e82a7097
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.scss
@@ -0,0 +1,32 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.year-selector {
+ overflow: auto;
+
+ display: flex;
+ flex-direction: row;
+
+ margin-bottom: 2.4rem;
+
+ padding-bottom: 2.4rem;
+
+ border-bottom: 0.1rem solid colors.$gray150;
+
+ box-sizing: border-box;
+ scrollbar-width: none;
+
+ position: relative;
+ scroll-snap-type: x proximity;
+
+ &__list {
+ width: max-content;
+
+ display: flex;
+
+ align-items: center;
+ justify-content: center;
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/index.js b/src/components/Calendar/components/DateSelectorComposer/index.js
new file mode 100644
index 00000000..4996a0d8
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/index.js
@@ -0,0 +1,114 @@
+import { Component } from 'pet-dex-utilities';
+import dayjs from 'dayjs';
+import { listenBreakpoint } from '../../../../utils/breakpoints/breakpoints';
+import { ModalController } from './utils/ModalController';
+import {
+ yearArrayGenerator,
+ monthArrayGenerator,
+} from './utils/arraysGenerators';
+import YearSelector from './components/YearSelector';
+import DateSelected from './components/DateSelected';
+import { MONTHS } from '../../utils/months';
+import MonthSelector from './components/MonthSelector';
+import './index.scss';
+
+const events = ['month:change', 'year:change'];
+
+const html = `
+
+`;
+
+export default function DateSelectorComposer({
+ month = dayjs().date() + 1,
+ year = dayjs().year(),
+}) {
+ Component.call(this, { html, events });
+
+ this.month = month;
+ this.year = year;
+ this.$dateSelector = this.selected.get('date-selector-composer');
+ this.$monthSelector = this.selected.get('month-selector');
+ this.$yearSelector = this.selected.get('year-selector');
+ this.modalControl = new ModalController(this);
+
+ const mountMobileSelectors = () => {
+ if (this.monthSelector) this.monthSelector.unmount();
+ if (this.yearSelector) this.yearSelector.unmount();
+
+ this.monthArray = monthArrayGenerator(this.month);
+ this.yearArray = yearArrayGenerator(this.year);
+
+ this.monthSelector = new MonthSelector({ dateArray: this.monthArray });
+ this.monthSelector.mount(this.$monthSelector);
+ this.monthSelector.listen('month:change', (newMonth) =>
+ this.setMonth(newMonth),
+ );
+
+ this.yearSelector = new YearSelector({ dateArray: this.yearArray });
+ this.yearSelector.mount(this.$yearSelector);
+ this.yearSelector.listen('year:change', (newYear) => this.setYear(newYear));
+ };
+
+ const mountDesktopSelectors = () => {
+ if (this.monthSelected) this.monthSelected.unmount();
+ if (this.yearSelected) this.yearSelected.unmount();
+
+ this.monthSelected = new DateSelected({ date: MONTHS[this.month] });
+ this.monthSelected.mount(this.$monthSelector);
+ this.monthSelected.listen('item:click', () => {
+ this.month = this.monthSelected.getDate();
+ this.monthArray = monthArrayGenerator(MONTHS.indexOf(this.month));
+ this.modalControl.onOpen(this.monthArray);
+ });
+
+ this.yearSelected = new DateSelected({ date: this.year });
+ this.yearSelected.mount(this.$yearSelector);
+ this.yearSelected.listen('item:click', () => {
+ this.year = this.yearSelected.getDate();
+ this.yearArray = yearArrayGenerator(this.year);
+ this.modalControl.onOpen(this.yearArray);
+ });
+ };
+
+ listenBreakpoint('from667', (matches) => {
+ if (matches) {
+ if (this.monthSelector) this.monthSelector.unmount();
+ if (this.yearSelector) this.yearSelector.unmount();
+
+ mountDesktopSelectors();
+ } else {
+ if (this.monthSelected) this.monthSelected.unmount();
+ if (this.yearSelected) this.yearSelected.unmount();
+ this.modalControl.onClose();
+
+ mountMobileSelectors();
+ }
+ });
+
+ window.addEventListener('click', (event) =>
+ this.modalControl.CloseOnClickOutside(event),
+ );
+}
+
+DateSelectorComposer.prototype = Object.assign(
+ DateSelectorComposer.prototype,
+ Component.prototype,
+ {
+ setMonth(month) {
+ if (this.monthSelected) this.monthSelected.setDate(MONTHS[month]);
+
+ this.month = month;
+ this.emit('month:change', month);
+ },
+
+ setYear(year) {
+ if (this.yearSelected) this.yearSelected.setDate(year);
+
+ this.year = year;
+ this.emit('year:change', year);
+ },
+ },
+);
diff --git a/src/components/Calendar/components/DateSelectorComposer/index.scss b/src/components/Calendar/components/DateSelectorComposer/index.scss
new file mode 100644
index 00000000..7033fd29
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/index.scss
@@ -0,0 +1,35 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.date-selector-composer {
+ width: 100%;
+
+ cursor: pointer;
+}
+
+@include breakpoints.from667 {
+ .date-selector-composer {
+ width: max-content;
+ min-width: 23.1rem;
+
+ display: flex;
+ flex-direction: row-reverse;
+ gap: 1rem;
+
+ align-items: center;
+ justify-content: center;
+
+ position: relative;
+
+ &::before {
+ width: 1.6rem;
+ height: 2.3rem;
+
+ background-image: url('../../images/arrows.svg');
+
+ content: '';
+ }
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/utils/ModalController.js b/src/components/Calendar/components/DateSelectorComposer/utils/ModalController.js
new file mode 100644
index 00000000..0cec0eb7
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/utils/ModalController.js
@@ -0,0 +1,39 @@
+import { MONTHS } from '../../../utils/months';
+import SelectorModal from '../components/SelectorModal';
+
+export class ModalController {
+ constructor(selector) {
+ this.selector = selector;
+ }
+
+ onOpen(dateArray) {
+ if (this.modal) this.onClose();
+
+ this.modal = new SelectorModal({ dateArray });
+ this.modal.mount(this.selector.$dateSelector);
+ this.modal.listen('date:change', (item) => this.changeDate(item));
+ }
+
+ changeDate(date) {
+ if (typeof date === 'string') {
+ this.selector.setMonth(MONTHS.indexOf(date));
+ }
+ if (typeof date === 'number') this.selector.setYear(date);
+ }
+
+ CloseOnClickOutside(event) {
+ const isOutside = !event
+ .composedPath()
+ .includes(this.selector.$dateSelector);
+
+ if (!isOutside) return;
+
+ this.onClose();
+ }
+
+ onClose() {
+ if (this.modal) {
+ this.modal.unmount();
+ }
+ }
+}
diff --git a/src/components/Calendar/components/DateSelectorComposer/utils/arraysGenerators.js b/src/components/Calendar/components/DateSelectorComposer/utils/arraysGenerators.js
new file mode 100644
index 00000000..cb47a1a4
--- /dev/null
+++ b/src/components/Calendar/components/DateSelectorComposer/utils/arraysGenerators.js
@@ -0,0 +1,18 @@
+import { MONTHS } from '../../../utils/months';
+
+export function monthArrayGenerator(month) {
+ const monthArray = new Array(103);
+ for (let i = 0; i < monthArray.length; i += 1) {
+ const monthIndex = (month - (3 - i) + 12) % 12;
+ monthArray[i] = MONTHS[monthIndex];
+ }
+ return monthArray;
+}
+
+export function yearArrayGenerator(year) {
+ const yearArray = new Array(101);
+ for (let i = 0; i < yearArray.length; i += 1) {
+ yearArray[i] = year - (50 - i);
+ }
+ return yearArray;
+}
diff --git a/src/components/Calendar/components/DayComposer/components/DayButton/index.js b/src/components/Calendar/components/DayComposer/components/DayButton/index.js
new file mode 100644
index 00000000..1d0595ba
--- /dev/null
+++ b/src/components/Calendar/components/DayComposer/components/DayButton/index.js
@@ -0,0 +1,57 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const events = ['day:active', 'day:previousMonth', 'day:nextMonth'];
+
+const html = `
+
+
+
+
+`;
+
+export default function DayButton(day, state) {
+ Component.call(this, { html, events });
+
+ this.day = day;
+ this.$day = this.selected.get('day');
+ this.$dayButton = this.selected.get('day-button');
+ this.setState(state);
+
+ this.$dayButton.innerText = day;
+ this.$dayButton.setAttribute('aria-label', `Dia ${this.day}`);
+
+ const emitClickevent = () => {
+ if (this.state === 'previousMonth') {
+ this.emit('day:previousMonth');
+ }
+
+ if (this.state === 'nextMonth') {
+ this.emit('day:nextMonth');
+ }
+
+ this.active();
+ };
+
+ this.$day.addEventListener('click', () => emitClickevent());
+}
+
+DayButton.prototype = Object.assign(DayButton.prototype, Component.prototype, {
+ setState(state) {
+ this.state = state;
+ this.$dayButton.classList.add(`day__button--${state}`);
+ },
+
+ active() {
+ this.$dayButton.classList.add('day__button--active');
+ this.emit('day:active', this.day);
+ },
+
+ desactive() {
+ this.$dayButton.classList.remove('day__button--active');
+ },
+
+ currentDay() {
+ this.$dayButton.classList.add('day__button--actual');
+ },
+});
diff --git a/src/components/Calendar/components/DayComposer/components/DayButton/index.scss b/src/components/Calendar/components/DayComposer/components/DayButton/index.scss
new file mode 100644
index 00000000..8e4be64f
--- /dev/null
+++ b/src/components/Calendar/components/DayComposer/components/DayButton/index.scss
@@ -0,0 +1,65 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.day {
+ width: 3.2rem;
+ height: 3.2rem;
+
+ justify-self: center;
+
+ &__button {
+ width: 100%;
+ height: 100%;
+
+ font-family: fonts.$primaryFont;
+ color: colors.$gray500;
+ text-align: center;
+ font-size: fonts.$xxs;
+ font-weight: fonts.$medium;
+
+ border: 0.1rem solid colors.$gray150;
+
+ background-color: colors.$secondary100;
+
+ border-radius: 10px;
+
+ cursor: pointer;
+
+ &:hover,
+ &--active {
+ color: colors.$primary200;
+ font-weight: fonts.$semiBold;
+
+ border: 0.1rem solid colors.$blue100;
+
+ background-color: colors.$blue150;
+ }
+
+ &--actual {
+ color: colors.$primary200;
+ font-weight: fonts.$semiBold;
+ }
+
+ &--previousMonth,
+ &--nextMonth {
+ color: rgba(160, 174, 192, 1);
+
+ border: 0.1rem solid colors.$gray150;
+
+ background-color: colors.$gray100;
+ }
+ }
+}
+
+@include breakpoints.from667 {
+ .day {
+ width: 5.4rem;
+ height: 5.4rem;
+
+ &__button {
+ font-size: fonts.$md;
+ }
+ }
+}
diff --git a/src/components/Calendar/components/DayComposer/index.js b/src/components/Calendar/components/DayComposer/index.js
new file mode 100644
index 00000000..2788ee30
--- /dev/null
+++ b/src/components/Calendar/components/DayComposer/index.js
@@ -0,0 +1,90 @@
+import { Component } from 'pet-dex-utilities';
+import dayjs from 'dayjs';
+import DayButton from './components/DayButton';
+import './index.scss';
+
+const events = ['day:change', 'day:nextMonth', 'day:previousMonth'];
+
+const html = `
+
+`;
+
+export default function DayComposer({
+ day = dayjs().date(),
+ month = dayjs().month() + 1,
+ year = dayjs().year(),
+}) {
+ Component.call(this, { html, events });
+
+ this.day = day;
+ this.month = month;
+ this.year = year;
+ this.$dayComposer = this.selected.get('day-composer');
+ this.activeDayButton = null;
+
+ const currentDay = dayjs().date();
+ const actualMonth = dayjs().month() + 1;
+ const actualYear = dayjs().year();
+
+ const totalDaysInCalendar = 42;
+ this.totalDaysInMonth = dayjs(`${this.year}-${this.month}-1`).daysInMonth();
+ this.firstDayInWeek = dayjs(`${this.year}-${this.month}-1`).day();
+ this.totalDaysInPreviousMonth = dayjs(
+ `${this.year}-${this.month - 1}-1`,
+ ).daysInMonth();
+ this.nextMonthDay = 1;
+ this.actualMonthDay = 1;
+
+ for (let i = 1; i <= totalDaysInCalendar; i += 1) {
+ if (i <= this.firstDayInWeek) {
+ const previousMonthDay =
+ this.totalDaysInPreviousMonth - this.firstDayInWeek + i;
+ this.mountDay(previousMonthDay, 'previousMonth');
+ } else if (this.actualMonthDay > this.totalDaysInMonth) {
+ this.mountDay(this.nextMonthDay, 'nextMonth');
+ this.nextMonthDay += 1;
+ } else {
+ this.mountDay(
+ this.actualMonthDay,
+ this.actualMonthDay === this.day && 'active',
+ );
+
+ if (
+ this.actualMonthDay === currentDay &&
+ this.month === actualMonth &&
+ this.year === actualYear
+ )
+ this.dayButton.currentDay();
+
+ this.actualMonthDay += 1;
+ }
+ }
+}
+
+DayComposer.prototype = Object.assign(
+ DayComposer.prototype,
+ Component.prototype,
+ {
+ mountDay(day, state) {
+ this.dayButton = new DayButton(day, state);
+
+ if (state === 'active') this.activeDayButton = this.dayButton;
+
+ this.dayButton.mount(this.$dayComposer);
+ this.dayButton.listen('day:active', (activeDay) =>
+ this.handleDayActive(this.dayButton, activeDay),
+ );
+ this.dayButton.listen('day:previousMonth', () =>
+ this.emit('day:previousMonth'),
+ );
+ this.dayButton.listen('day:nextMonth', () => this.emit('day:nextMonth'));
+ },
+
+ handleDayActive(dayButton, activeDay) {
+ if (this.activeDayButton) this.activeDayButton.desactive();
+
+ this.activeDayButton = dayButton;
+ this.emit('day:change', activeDay);
+ },
+ },
+);
diff --git a/src/components/Calendar/components/DayComposer/index.scss b/src/components/Calendar/components/DayComposer/index.scss
new file mode 100644
index 00000000..f6bab690
--- /dev/null
+++ b/src/components/Calendar/components/DayComposer/index.scss
@@ -0,0 +1,16 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.day-composer {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 0.2rem;
+
+ list-style: none;
+
+ @include breakpoints.from360 {
+ gap: 0.8rem;
+ }
+}
diff --git a/src/components/Calendar/components/NavigationButton/index.js b/src/components/Calendar/components/NavigationButton/index.js
new file mode 100644
index 00000000..752eed91
--- /dev/null
+++ b/src/components/Calendar/components/NavigationButton/index.js
@@ -0,0 +1,41 @@
+import { Component } from 'pet-dex-utilities';
+import arrow from '../../images/nav-button.svg';
+
+import './index.scss';
+
+const events = ['button:click'];
+
+const html = `
+
+`;
+
+export default function NavigationButton(state) {
+ Component.call(this, { html, events });
+
+ this.state = state;
+ this.$navigationButton = this.selected.get('navigation-button');
+ this.$buttonIcon = this.selected.get('button-icon');
+
+ this.$buttonIcon.classList.toggle(
+ 'navigation-button__icon--next',
+ this.state === 'next',
+ );
+ this.$navigationButton.setAttribute(
+ 'aria-label',
+ this.state === 'next' ? 'Ir para o próximo mês' : 'Ir para o mês anterior',
+ );
+
+ this.emitEvent = () => {
+ this.emit('button:click');
+ };
+
+ this.$navigationButton.addEventListener('click', this.emitEvent);
+}
+
+NavigationButton.prototype = Object.assign(
+ NavigationButton.prototype,
+ Component.prototype,
+ {},
+);
diff --git a/src/components/Calendar/components/NavigationButton/index.scss b/src/components/Calendar/components/NavigationButton/index.scss
new file mode 100644
index 00000000..854e2e80
--- /dev/null
+++ b/src/components/Calendar/components/NavigationButton/index.scss
@@ -0,0 +1,36 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.navigation-button {
+ width: 4.6rem;
+ height: 4.6rem;
+
+ display: none;
+
+ align-items: center;
+ justify-content: center;
+
+ border: 0.1rem solid rgb(160, 174, 192);
+
+ background-color: colors.$secondary100;
+
+ border-radius: 14px;
+
+ cursor: pointer;
+
+ &__icon {
+ color: rgb(160, 174, 192);
+
+ &--next {
+ transform: rotate(180deg);
+ }
+ }
+}
+
+@include breakpoints.from667 {
+ .navigation-button {
+ display: flex;
+ }
+}
diff --git a/src/components/Calendar/components/WeekDayComposer/components/WeekDay/index.js b/src/components/Calendar/components/WeekDayComposer/components/WeekDay/index.js
new file mode 100644
index 00000000..fe9879f8
--- /dev/null
+++ b/src/components/Calendar/components/WeekDayComposer/components/WeekDay/index.js
@@ -0,0 +1,19 @@
+import { Component } from 'pet-dex-utilities';
+import './index.scss';
+
+const events = [];
+
+const html = `
+
+`;
+
+export default function WeekDay(weekDay) {
+ Component.call(this, { events, html });
+
+ this.weekDay = weekDay;
+ this.$weekDay = this.selected.get('week-day');
+ this.$weekDay.innerText = weekDay.abbreviation;
+ this.$weekDay.setAttribute('aria-label', `${weekDay.name}`);
+}
+
+WeekDay.prototype = Object.assign(WeekDay.prototype, Component.prototype, {});
diff --git a/src/components/Calendar/components/WeekDayComposer/components/WeekDay/index.scss b/src/components/Calendar/components/WeekDayComposer/components/WeekDay/index.scss
new file mode 100644
index 00000000..85a5b178
--- /dev/null
+++ b/src/components/Calendar/components/WeekDayComposer/components/WeekDay/index.scss
@@ -0,0 +1,23 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.week-days__day {
+ font-family: fonts.$fourthFont;
+ color: rgb(160, 174, 192);
+ text-align: center;
+ font-size: 1rem;
+ font-weight: fonts.$regular;
+
+ &--active {
+ color: colors.$primary200;
+ font-weight: fonts.$semiBold;
+ }
+}
+
+@include breakpoints.from667 {
+ .week-days__day {
+ font-size: fonts.$xs;
+ }
+}
diff --git a/src/components/Calendar/components/WeekDayComposer/index.js b/src/components/Calendar/components/WeekDayComposer/index.js
new file mode 100644
index 00000000..b782347a
--- /dev/null
+++ b/src/components/Calendar/components/WeekDayComposer/index.js
@@ -0,0 +1,38 @@
+import { Component } from 'pet-dex-utilities';
+import WeekDay from './components/WeekDay';
+import { WEEK_DAYS } from './utils/weekDays';
+
+import './index.scss';
+
+const events = [];
+
+const html = `
+
+`;
+
+export default function WeekDayComposer() {
+ Component.call(this, { html, events });
+
+ this.$weekDays = this.selected.get('week-days');
+ this.totalWeekDays = 7;
+
+ for (let i = 0; i < this.totalWeekDays; i += 1) {
+ const weekDay = new WeekDay(WEEK_DAYS[i]);
+ weekDay.mount(this.$weekDays);
+ }
+}
+
+WeekDayComposer.prototype = Object.assign(
+ WeekDayComposer.prototype,
+ Component.prototype,
+ {
+ activeWeekDay(currentWeekDay) {
+ Array.from(this.$weekDays.children).forEach((weekDay, index) => {
+ weekDay.classList.toggle(
+ 'week-days__day--active',
+ index === currentWeekDay,
+ );
+ });
+ },
+ },
+);
diff --git a/src/components/Calendar/components/WeekDayComposer/index.scss b/src/components/Calendar/components/WeekDayComposer/index.scss
new file mode 100644
index 00000000..25986ce9
--- /dev/null
+++ b/src/components/Calendar/components/WeekDayComposer/index.scss
@@ -0,0 +1,7 @@
+.week-days {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 0.8rem;
+
+ margin-bottom: 1.6rem;
+}
diff --git a/src/components/Calendar/components/WeekDayComposer/utils/weekDays.js b/src/components/Calendar/components/WeekDayComposer/utils/weekDays.js
new file mode 100644
index 00000000..9c3f7ab0
--- /dev/null
+++ b/src/components/Calendar/components/WeekDayComposer/utils/weekDays.js
@@ -0,0 +1,36 @@
+export const WEEK_DAYS = [
+ {
+ name: 'Domingo',
+ abbreviation: 'Dom',
+ },
+
+ {
+ name: 'Segunda-Feira',
+ abbreviation: 'Seg',
+ },
+
+ {
+ name: 'Terça-Feira',
+ abbreviation: 'Ter',
+ },
+
+ {
+ name: 'Quarta-Feira',
+ abbreviation: 'Qua',
+ },
+
+ {
+ name: 'Quinta-Feira',
+ abbreviation: 'Qui',
+ },
+
+ {
+ name: 'Sexta-Feira',
+ abbreviation: 'Sex',
+ },
+
+ {
+ name: 'Sábado',
+ abbreviation: 'Sáb',
+ },
+];
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/nav-button.svg b/src/components/Calendar/images/nav-button.svg
new file mode 100644
index 00000000..ea74a960
--- /dev/null
+++ b/src/components/Calendar/images/nav-button.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..00be72e6
--- /dev/null
+++ b/src/components/Calendar/index.js
@@ -0,0 +1,174 @@
+import { Component } from 'pet-dex-utilities';
+import dayjs from 'dayjs';
+import WeekDayComposer from './components/WeekDayComposer';
+import NavigationButton from './components/NavigationButton';
+import DateSelectorComposer from './components/DateSelectorComposer';
+import DayComposer from './components/DayComposer';
+
+import './index.scss';
+
+const events = [
+ 'date:change',
+ 'day:change',
+ 'month:change',
+ 'year:change',
+ 'month:next',
+ 'month:previous',
+];
+
+const html = `
+
+`;
+
+export default function Calendar({
+ day = dayjs().date(),
+ month = dayjs().month() + 1,
+ year = dayjs().year(),
+}) {
+ Component.call(this, { html, events });
+
+ this.day = day;
+ this.month = month;
+ this.year = year;
+
+ this.$calendar = this.selected.get('calendar');
+ this.$calendarControls = this.selected.get('calendar-controls');
+ this.$calendarContent = this.selected.get('calendar-content');
+
+ this.previousButton = new NavigationButton('previous');
+ this.previousButton.mount(this.$calendarControls);
+ this.previousButton.listen('button:click', () => this.previousMonth());
+
+ this.dateSelector = new DateSelectorComposer({
+ month: this.month - 1,
+ year: this.year,
+ });
+ this.dateSelector.mount(this.$calendarControls);
+ this.dateSelector.listen('month:change', (newMonth) => {
+ this.setMonth(newMonth + 1);
+ });
+ this.dateSelector.listen('year:change', (newYear) => this.setYear(newYear));
+
+ this.nextButton = new NavigationButton('next');
+ this.nextButton.mount(this.$calendarControls);
+ this.nextButton.listen('button:click', () => this.nextMonth());
+
+ this.weekDayComposer = new WeekDayComposer();
+ this.weekDayComposer.mount(this.$calendarContent);
+
+ this.mountDayComposer = () => {
+ if (this.dayComposer) this.dayComposer.unmount();
+
+ this.dayComposer = new DayComposer(this.getDate());
+ this.dayComposer.mount(this.$calendarContent);
+ this.dayComposer.listen('day:change', (newDay) => this.setDay(newDay));
+ this.dayComposer.listen('day:previousMonth', () => this.previousMonth());
+ this.dayComposer.listen('day:nextMonth', () => this.nextMonth());
+ };
+
+ this.setDate(this.day, this.month, this.year);
+}
+
+Calendar.prototype = Object.assign(Calendar.prototype, Component.prototype, {
+ setDate(day, month, year) {
+ this.day = day;
+ this.month = month;
+ this.year = year;
+
+ this.mountDayComposer();
+ this.firstDayInWeek = dayjs(`${this.year}-${this.month}-${this.day}`).day();
+ this.weekDayComposer.activeWeekDay(this.firstDayInWeek);
+
+ this.emit('date:change', {
+ day: this.day,
+ month: this.month,
+ year: this.year,
+ });
+ },
+
+ setDay(day) {
+ this.day = day;
+ this.setDate(this.day, this.month, this.year);
+
+ this.emit('day:change', this.day);
+ },
+
+ setMonth(month) {
+ this.month = month;
+ this.setDate(this.day, this.month, this.year);
+
+ this.emit('month:change', this.month);
+ },
+
+ setYear(year) {
+ this.year = year;
+ this.setDate(this.day, this.month, this.year);
+
+ this.emit('year:change', this.year);
+ },
+
+ getDate() {
+ return {
+ day: this.day,
+ month: this.month,
+ year: this.year,
+ };
+ },
+
+ getDay() {
+ return this.day;
+ },
+
+ getMonth() {
+ return this.month;
+ },
+
+ getYear() {
+ return this.year;
+ },
+
+ nextMonth(day) {
+ this.day = day || this.day;
+ this.month += 1;
+
+ if (this.month > 12) {
+ this.month = 1;
+ this.year += 1;
+ this.dateSelector.setYear(this.year);
+ }
+
+ const totalDaysInMonth = dayjs(
+ `${this.year}-${this.month}-1`,
+ ).daysInMonth();
+ if (this.day > totalDaysInMonth) this.day = totalDaysInMonth;
+
+ this.dateSelector.setMonth(this.month - 1);
+ this.setDate(this.day, this.month, this.year);
+
+ this.emit('month:next', this.month);
+ },
+
+ previousMonth(day) {
+ this.day = day || this.day;
+ this.month -= 1;
+
+ if (this.month < 1) {
+ this.month = 12;
+ this.year -= 1;
+ this.dateSelector.setYear(this.year);
+ }
+
+ const totalDaysInMonth = dayjs(
+ `${this.year}-${this.month}-1`,
+ ).daysInMonth();
+ if (this.day > totalDaysInMonth) this.day = totalDaysInMonth;
+
+ this.dateSelector.setMonth(this.month - 1);
+ this.setDate(this.day, this.month, this.year);
+
+ this.emit('month:previous', this.year);
+ },
+});
diff --git a/src/components/Calendar/index.scss b/src/components/Calendar/index.scss
new file mode 100644
index 00000000..979e2c8b
--- /dev/null
+++ b/src/components/Calendar/index.scss
@@ -0,0 +1,16 @@
+@use '~styles/base.scss';
+@use '~styles/colors.scss' as colors;
+@use '~styles/fonts.scss' as fonts;
+@use '~styles/breakpoints.scss' as breakpoints;
+
+.calendar {
+ &__controls {
+ display: flex;
+
+ align-items: center;
+
+ justify-content: space-between;
+
+ margin-bottom: 2.4rem;
+ }
+}
diff --git a/src/components/Calendar/utils/months.js b/src/components/Calendar/utils/months.js
new file mode 100644
index 00000000..956f6c9a
--- /dev/null
+++ b/src/components/Calendar/utils/months.js
@@ -0,0 +1,14 @@
+export const MONTHS = [
+ 'Janeiro',
+ 'Fevereiro',
+ 'Março',
+ 'Abril',
+ 'Maio',
+ 'Junho',
+ 'Julho',
+ 'Agosto',
+ 'Setembro',
+ 'Outubro',
+ 'Novembro',
+ 'Dezembro',
+];
diff --git a/src/layouts/components/SideMenu/index.scss b/src/layouts/components/SideMenu/index.scss
index 3ea4b4d3..959ae2de 100644
--- a/src/layouts/components/SideMenu/index.scss
+++ b/src/layouts/components/SideMenu/index.scss
@@ -84,31 +84,6 @@
font-style: fonts.$normal;
}
- &__avatars-yourpet {
- max-height: calc(8.5rem * 2 + 2rem);
- overflow-y: auto;
-
- display: grid;
- grid-template-columns: repeat(3, 6rem);
- gap: 2rem;
-
- margin-top: 2rem;
- padding: 0.4rem 0.2rem;
-
- &::-webkit-scrollbar {
- width: 0.4rem;
- }
-
- &::-webkit-scrollbar-track {
- background: colors.$primary600;
- }
-
- &::-webkit-scrollbar-thumb {
- background-color: colors.$primary700;
- border-radius: 1rem;
- }
- }
-
&__itens {
font-family: fonts.$primaryFont;
color: colors.$secondary100;
diff --git a/src/stories/Calendar.stories.js b/src/stories/Calendar.stories.js
new file mode 100644
index 00000000..55c03cc2
--- /dev/null
+++ b/src/stories/Calendar.stories.js
@@ -0,0 +1,20 @@
+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 = {
+ args: {
+ day: 1,
+ month: 1,
+ year: 2024,
+ },
+};
diff --git a/src/styles/colors.scss b/src/styles/colors.scss
index a2bddb97..b522f0ea 100644
--- a/src/styles/colors.scss
+++ b/src/styles/colors.scss
@@ -25,8 +25,8 @@ $error100: rgb(179, 38, 30);
$white: rgb(255, 255, 255);
-// neutrals (Gray)
-$gray100: rgb(236, 239, 242);
+// neutrals (Grey)
+$gray100: rgb(247, 250, 252);
$gray150: rgb(236, 239, 242);
$gray200: rgb(224, 224, 224);
$gray250: rgb(172, 172, 181);
@@ -34,7 +34,7 @@ $gray300: rgb(179, 190, 205);
$gray400: rgb(141, 141, 141);
$gray500: rgb(96, 104, 115);
$gray600: rgb(102, 116, 121);
-$gray700: rgb(6, 8, 9);
+$gray700: rgb(128, 139, 154);
$gray800: rgb(57, 67, 79);
$gray900: rgb(32, 35, 38);
@@ -49,4 +49,6 @@ $shade100: rgb(0, 0, 0);
// custom
+$blue100: rgb(209, 230, 255);
+$blue150: rgba(209, 230, 255, 0.5);
$blue600: rgb(18, 104, 204);