diff --git a/src/components/Button/index.scss b/src/components/Button/index.scss index 61d804ba..6d427cdf 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: 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; + } +} 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..2bec0d34 --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.js @@ -0,0 +1,47 @@ +import { Component } from 'pet-dex-utilities'; +import './index.scss'; +import SelectorItem from '../SelectorItem'; + +const events = ['selector:click']; + +const html = ` +
+ + + +
+`; + +export default function MonthSelector(monthArray) { + Component.call(this, { html, events }); + + this.monthArray = monthArray; + this.$monthSelector = this.selected.get('month-selector'); + this.$previousMonths = this.selected.get('previous-months'); + this.$currentMonth = this.selected.get('current-month'); + this.$nextMonths = this.selected.get('next-months'); + + for (let i = 0; i < this.monthArray.length; i += 1) { + if (i < 3) { + const selectorItem = new SelectorItem(this.monthArray[i]); + selectorItem.mount(this.$previousMonths); + } + if (i === 3) { + this.$currentMonth.innerText = this.monthArray[i]; + } + if (i > 3) { + const selectorItem = new SelectorItem(this.monthArray[i]); + selectorItem.mount(this.$nextMonths); + } + } + + this.$currentMonth.addEventListener('click', () => + this.emit('selector:click'), + ); +} + +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..413d164c --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/MonthSelector/index.scss @@ -0,0 +1,74 @@ +@use '~styles/base.scss'; +@use '~styles/colors.scss' as colors; +@use '~styles/fonts.scss' as fonts; +@use '~styles/breakpoints.scss' as breakpoints; + +.month-selector { + width: 100%; + + display: flex; + gap: 2.4rem; + + align-items: center; + justify-content: center; + + margin-bottom: 2.4rem; + + padding-bottom: 2.4rem; + + border-bottom: 0.1rem solid colors.$gray150; + + &__previous-months, + &__next-months { + display: flex; + gap: 1.6rem; + + align-items: center; + justify-content: center; + } + + &__current-month { + align-self: center; + + font-family: fonts.$primaryFont; + color: colors.$primary200; + font-size: fonts.$lg; + font-weight: fonts.$semiBold; + + padding: 0.6rem 1.2rem; + border: 0.1rem solid colors.$blue100; + + background-color: colors.$blue150; + border-radius: 1.4rem; + } +} + +@include breakpoints.from667 { + .month-selector { + width: max-content; + + margin-bottom: 0; + padding-bottom: 0; + border-bottom: 0; + + &__previous-months, + &__next-months { + display: none; + } + + &__current-month { + color: colors.$gray800; + font-weight: fonts.$regular; + + padding: 0; + border: 0; + + background-color: colors.$secondary100; + + &:hover { + color: colors.$primary200; + font-weight: fonts.$semiBold; + } + } + } +} 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..f5286842 --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.js @@ -0,0 +1,25 @@ +import { Component } from 'pet-dex-utilities'; +import './index.scss'; + +const events = []; + +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; +} + +SelectorItem.prototype = Object.assign( + SelectorItem.prototype, + Component.prototype, + { + active() { + this.$selectorItem.classList.add('selector-item--active'); + }, + }, +); 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..281fa15a --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorItem/index.scss @@ -0,0 +1,11 @@ +@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; + font-size: fonts.$xs; + font-weight: fonts.$medium; +} 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..2b0df598 --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/index.js @@ -0,0 +1,23 @@ +import { Component } from 'pet-dex-utilities'; +import './index.scss'; + +const events = ['item:change']; + +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; +} + +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..7bbde514 --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/components/ModalItem/index.scss @@ -0,0 +1,43 @@ +@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 { + width: 100%; + + 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..2f98acfd --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.js @@ -0,0 +1,99 @@ +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) { + 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; + this.rowHeight = 40; + this.nodePadding = 5; + this.scrollTop = this.$selectorModal.scrollTop; + + setTimeout(() => { + this.viewportHeight = this.$selectorModal.offsetHeight; + + const renderWindow = () => { + this.totalContentHeight = this.itemCount * this.rowHeight; + + this.startNode = + Math.floor(this.scrollTop / this.rowHeight) - this.nodePadding; + this.startNode = Math.max(0, this.startNode); + + this.visibleNodesCount = + Math.ceil(this.viewportHeight / this.rowHeight) + 2 * this.nodePadding; + this.visibleNodesCount = Math.min( + this.itemCount - this.startNode, + this.visibleNodesCount, + ); + + this.offsetY = this.startNode * this.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(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(); + }); + }); + + this.$listContent.scrollIntoView(true); + + const scrollToMiddle = () => { + this.scrollTop = + this.totalContentHeight / 2 - this.viewportHeight / 2 - 48; + 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..7c1bcb3f --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/index.scss @@ -0,0 +1,47 @@ +@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; + + 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; +} + +@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/SelectorModal/utils/scrollModal.js b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/utils/scrollModal.js new file mode 100644 index 00000000..2863f25c --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/SelectorModal/utils/scrollModal.js @@ -0,0 +1,24 @@ +import { MONTHS } from '../../../../../utils/months'; + +export function scrollModal(selector) { + const { selectorModal } = selector; + + const handleScroll = (event) => { + event.preventDefault(); + const isScrollNext = event.deltaY > 0; + + if (selector.isYear) { + const nextYear = +selector.items[3].innerText; + const prevYear = +selector.items[1].innerText; + const newYear = isScrollNext ? nextYear : prevYear; + selector.changeYear(newYear); + } else { + const nextMonth = MONTHS.indexOf(selector.items[3].innerText); + const prevMonth = MONTHS.indexOf(selector.items[1].innerText); + const newMonth = isScrollNext ? nextMonth : prevMonth; + selector.changeMonth(newMonth); + } + }; + + selectorModal.addEventListener('wheel', handleScroll); +} 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..17e7f84c --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.js @@ -0,0 +1,47 @@ +import { Component } from 'pet-dex-utilities'; +import './index.scss'; +import SelectorItem from '../SelectorItem'; + +const events = ['selector:click']; + +const html = ` +
    + + + +
    +`; + +export default function YearSelector(yearArray) { + Component.call(this, { html, events }); + + this.yearArray = yearArray; + this.$monthSelector = this.selected.get('year-selector'); + this.$previousYears = this.selected.get('previous-years'); + this.$currentYear = this.selected.get('current-year'); + this.$nextYears = this.selected.get('next-years'); + + for (let i = 0; i < this.yearArray.length; i += 1) { + if (i < 50) { + const selectorItem = new SelectorItem(this.yearArray[i]); + selectorItem.mount(this.$previousYears); + } + if (i === 50) { + this.$currentYear.innerText = this.yearArray[i]; + } + if (i > 50) { + const selectorItem = new SelectorItem(this.yearArray[i]); + selectorItem.mount(this.$nextYears); + } + } + + this.$currentYear.addEventListener('click', () => + this.emit('selector:click'), + ); +} + +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..d473e909 --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/components/YearSelector/index.scss @@ -0,0 +1,74 @@ +@use '~styles/base.scss'; +@use '~styles/colors.scss' as colors; +@use '~styles/fonts.scss' as fonts; +@use '~styles/breakpoints.scss' as breakpoints; + +.year-selector { + width: 100%; + + display: flex; + gap: 2.4rem; + + align-items: center; + justify-content: center; + + margin-bottom: 2.4rem; + + padding-bottom: 2.4rem; + + border-bottom: 0.1rem solid colors.$gray150; + + &__previous-years, + &__next-years { + display: flex; + gap: 1.6rem; + + align-items: center; + justify-content: center; + } + + &__current-year { + align-self: center; + + font-family: fonts.$primaryFont; + color: colors.$primary200; + font-size: fonts.$lg; + font-weight: fonts.$semiBold; + + padding: 0.6rem 1.2rem; + border: 0.1rem solid colors.$blue100; + + background-color: colors.$blue150; + border-radius: 1.4rem; + } +} + +@include breakpoints.from667 { + .year-selector { + width: max-content; + + margin-bottom: 0; + padding-bottom: 0; + border-bottom: 0; + + &__previous-years, + &__next-years { + display: none; + } + + &__current-year { + color: colors.$gray800; + font-weight: fonts.$regular; + + padding: 0; + border: 0; + + background-color: colors.$secondary100; + + &:hover { + color: colors.$primary200; + font-weight: fonts.$semiBold; + } + } + } +} diff --git a/src/components/Calendar/components/DateSelectorComposer/index.js b/src/components/Calendar/components/DateSelectorComposer/index.js new file mode 100644 index 00000000..2e5d982d --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/index.js @@ -0,0 +1,76 @@ +import { Component } from 'pet-dex-utilities'; +import './index.scss'; +import MonthSelector from './components/MonthSelector'; +import { ModalController } from './utils/ModalController'; +import { + yearArrayGenerator, + monthArrayGenerator, +} from './utils/arraysGenerators'; +import YearSelector from './components/YearSelector'; + +const events = ['month:change', 'year:change']; + +const html = ` +
    +
    +
    +
    +`; + +export default function DateSelectorComposer(month, year) { + Component.call(this, { html, events }); + + this.month = month; + this.year = year; + this.$dateSelector = this.selected.get('date-selector'); + this.$monthSelector = this.selected.get('month-selector'); + this.$yearSelector = this.selected.get('year-selector'); + this.modalControl = new ModalController(this); + + this.mountYearSelector = () => { + if (this.yearSelector) this.yearSelector.unmount(); + + this.yearArray = yearArrayGenerator(this.year); + this.yearSelector = new YearSelector(this.yearArray); + this.yearSelector.mount(this.$yearSelector); + this.yearSelector.listen('selector:click', () => + this.modalControl.Open(this.yearArray), + ); + }; + + this.mountMonthSelector = () => { + if (this.monthSelector) this.monthSelector.unmount(); + + this.monthArray = monthArrayGenerator(this.month); + this.monthSelector = new MonthSelector(this.monthArray); + this.monthSelector.mount(this.$monthSelector); + this.monthSelector.listen('selector:click', () => + this.modalControl.Open(this.monthArray), + ); + }; + + this.mountYearSelector(); + this.mountMonthSelector(); + + window.addEventListener('click', (event) => + this.modalControl.CloseOnClickOutside(event), + ); +} + +DateSelectorComposer.prototype = Object.assign( + DateSelectorComposer.prototype, + Component.prototype, + { + setMonth(month) { + this.month = month; + this.mountMonthSelector(); + this.emit('month:change', month); + }, + + setYear(year) { + this.year = year; + this.mountYearSelector(); + 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..b918fb5c --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/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; + +.date-selector { + width: 100%; + + cursor: pointer; +} + +@include breakpoints.from667 { + .date-selector { + width: 10%; + min-width: 23.1rem; + + display: flex; + flex-direction: row-reverse; + gap: 1rem; + + 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..2fd318d2 --- /dev/null +++ b/src/components/Calendar/components/DateSelectorComposer/utils/ModalController.js @@ -0,0 +1,32 @@ +import SelectorModal from '../components/SelectorModal'; + +export class ModalController { + constructor(selector) { + this.selector = selector; + } + + Open(dateArray) { + if (this.modal) this.Close(); + + this.modal = new SelectorModal(dateArray); + this.modal.mount(this.selector.$dateSelector); + // this.modal.listen('month:change', (month) => this.selector.setMonth(month)); + this.modal.listen('date:change', (item) => this.selector.setYear(item)); + } + + CloseOnClickOutside(event) { + const isOutside = !event + .composedPath() + .includes(this.selector.$dateSelector); + + if (!isOutside) return; + + this.Close(); + } + + Close() { + 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..41f4e7ca --- /dev/null +++ b/src/components/Calendar/components/DayComposer/components/DayButton/index.js @@ -0,0 +1,39 @@ +import { Component } from 'pet-dex-utilities'; +import './index.scss'; + +const events = ['day:active', 'day:previousMonth', 'day:nextMonth']; + +const html = ` +
  • + +
  • + +`; + +export default function DayButton(day) { + Component.call(this, { html, events }); + + this.day = day; + this.$day = this.selected.get('day'); + this.$dayButton = this.selected.get('day-button'); + + this.$dayButton.innerText = day; + this.$dayButton.setAttribute('aria-label', `Dia ${this.day}`); + + this.$day.addEventListener('click', () => this.active()); +} + +DayButton.prototype = Object.assign(DayButton.prototype, Component.prototype, { + setState(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'); + }, +}); 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..26e153e3 --- /dev/null +++ b/src/components/Calendar/components/DayComposer/components/DayButton/index.scss @@ -0,0 +1,60 @@ +@use '~styles/base.scss'; +@use '~styles/colors.scss' as colors; +@use '~styles/fonts.scss' as fonts; +@use '~styles/breakpoints.scss' as breakpoints; + +.day { + width: 4rem; + height: 4rem; + + justify-self: center; + + &__button { + width: 100%; + height: 100%; + + font-family: fonts.$primaryFont; + color: colors.$gray500; + text-align: center; + font-size: fonts.$sm; + 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; + } + + &--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..f41b5c90 --- /dev/null +++ b/src/components/Calendar/components/DayComposer/index.js @@ -0,0 +1,72 @@ +import { Component } from 'pet-dex-utilities'; +import dayjs from 'dayjs'; +import DayButton from './components/DayButton'; + +import './index.scss'; + +const events = ['day:change']; + +const html = ` +
    +`; + +export default function DayComposer({ day, month, 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; + + this.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 <= this.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', + ); + this.actualMonthDay += 1; + } + } +} + +DayComposer.prototype = Object.assign( + DayComposer.prototype, + Component.prototype, + { + mountDay(day, state) { + const dayButton = new DayButton(day); + + if (state === 'active') this.activeDayButton = dayButton; + + dayButton.mount(this.$dayComposer); + dayButton.setState(state); + dayButton.listen('day:active', (activeDay) => + this.handleDayActive(dayButton, activeDay), + ); + }, + + 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..dcb6072c --- /dev/null +++ b/src/components/Calendar/components/DayComposer/index.scss @@ -0,0 +1,7 @@ +.day-composer { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 0.8rem; + + list-style: none; +} diff --git a/src/components/Calendar/components/NavigationButton/index.js b/src/components/Calendar/components/NavigationButton/index.js new file mode 100644 index 00000000..ae5ab19a --- /dev/null +++ b/src/components/Calendar/components/NavigationButton/index.js @@ -0,0 +1,41 @@ +import { Component } from 'pet-dex-utilities'; +import arrow from '../../images/navButton.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..0cf72fef --- /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: 1.2rem; + 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..658de79c --- /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: 'Seg', + }, +]; 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..26936c84 --- /dev/null +++ b/src/components/Calendar/index.js @@ -0,0 +1,142 @@ +import { Component } from 'pet-dex-utilities'; +import dayjs from 'dayjs'; +import WeekDayComposer from './components/WeekDayComposer'; +import NavigationButton from './components/NavigationButton'; + +import './index.scss'; +import DateSelectorComposer from './components/DateSelectorComposer'; +import DayComposer from './components/DayComposer'; + +const events = []; + +const html = ` +
    +
    +
    +
    +`; + +export default function Calendar({ day, month, year }) { + Component.call(this, { html, events }); + + this.day = day || dayjs().date(); + this.month = month || dayjs().month() + 1; + this.year = year || dayjs().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(this.month - 1, 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.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); + }, + + getDate() { + return { + day: this.day, + month: this.month, + year: this.year, + }; + }, + + setDay(day) { + this.day = day; + this.setDate(this.day, this.month, this.year); + }, + + getDay() { + return this.day; + }, + + setMonth(month) { + this.month = month; + this.setDate(this.day, this.month, this.year); + }, + + getMonth() { + return this.month; + }, + + setYear(year) { + this.year = year; + this.setDate(this.day, this.month, this.year); + }, + + 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); + }, + + 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); + }, +}); 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/app/components/SideMenu/index.scss b/src/layouts/app/components/SideMenu/index.scss index 0953bb8f..68d4cc38 100644 --- a/src/layouts/app/components/SideMenu/index.scss +++ b/src/layouts/app/components/SideMenu/index.scss @@ -1,147 +1,147 @@ -@use '~styles/colors.scss' as colors; -@use '~styles/breakpoints.scss' as breakpoints; -@use '~styles/fonts.scss' as fonts; - -.side-menu-nav { - display: flex; - - align-items: center; - justify-content: space-between; - padding-inline: 2rem; - - &__logo-container { - display: none; - padding-block: 4rem 2rem; - - text-align: center; - } - - &__logo { - max-width: 100%; - } - - &__icons { - display: flex; - gap: 2.4rem; - - align-items: center; - justify-content: center; - } - - &__notifications { - width: 1.6rem; - height: 1.95rem; - } - - &__perfil { - width: 4.3rem; - height: 4.3rem; - - border-radius: 100%; - } - - &__exit { - width: 2rem; - height: 2rem; - } - - &__exitmenu { - width: 2.9rem; - height: 2.4rem; - } -} - -.side-menu-content { - width: 28rem; - - display: flex; - flex-direction: column; - gap: 4.2rem; - - margin: 0 auto; - - &__line { - width: 100%; - - border: 0.1rem solid colors.$secondary100; - } - - &__lineinside { - width: 100%; - - margin: 4.2rem 0; - border: 0.1rem solid colors.$secondary100; - } - - &__yourpet { - font-family: fonts.$primaryFont; - } - - &__title-yourpet { - color: colors.$secondary100; - font-size: 1.6rem; - font-weight: fonts.$bold; - font-style: fonts.$normal; - } - - &__itens { - font-family: fonts.$primaryFont; - color: colors.$secondary100; - font-size: 1.6rem; - } - - &__ul { - display: flex; - flex-direction: column; - gap: 0.8rem; - - align-items: flex-start; - } - - &__menuitens { - display: flex; - gap: 1.2rem; - - align-items: center; - - color: colors.$secondary100; - text-decoration: none; - - padding: 1.2rem; - - &--active { - background-color: rgb(0, 29, 49); - border-radius: 2rem; - } - } -} - -@include breakpoints.from1024() { - .side-menu-nav { - &__logo-container { - display: block; - - margin: 0 auto; - } - - &__icons { - display: none; - } - - &__exitmenu { - display: none; - } - } - - .side-menu-content { - width: 70%; - - &__menuitens { - transition: 0.3s; - - &:hover { - opacity: 0.6; - } - } - } -} +@use '~styles/colors.scss' as colors; +@use '~styles/breakpoints.scss' as breakpoints; +@use '~styles/fonts.scss' as fonts; + +.side-menu-nav { + display: flex; + + align-items: center; + justify-content: space-between; + padding-inline: 2rem; + + &__logo-container { + display: none; + padding-block: 4rem 2rem; + + text-align: center; + } + + &__logo { + max-width: 100%; + } + + &__icons { + display: flex; + gap: 2.4rem; + + align-items: center; + justify-content: center; + } + + &__notifications { + width: 1.6rem; + height: 1.95rem; + } + + &__perfil { + width: 4.3rem; + height: 4.3rem; + + border-radius: 100%; + } + + &__exit { + width: 2rem; + height: 2rem; + } + + &__exitmenu { + width: 2.9rem; + height: 2.4rem; + } +} + +.side-menu-content { + width: 28rem; + + display: flex; + flex-direction: column; + gap: 4.2rem; + + margin: 0 auto; + + &__line { + width: 100%; + + border: 0.1rem solid colors.$secondary100; + } + + &__lineinside { + width: 100%; + + margin: 4.2rem 0; + border: 0.1rem solid colors.$secondary100; + } + + &__yourpet { + font-family: fonts.$primaryFont; + } + + &__title-yourpet { + color: colors.$secondary100; + font-size: 1.6rem; + font-weight: fonts.$bold; + font-style: fonts.$normal; + } + + &__itens { + font-family: fonts.$primaryFont; + color: colors.$secondary100; + font-size: 1.6rem; + } + + &__ul { + display: flex; + flex-direction: column; + gap: 0.8rem; + + align-items: flex-start; + } + + &__menuitens { + display: flex; + gap: 1.2rem; + + align-items: center; + + color: colors.$secondary100; + text-decoration: none; + + padding: 1.2rem; + + &--active { + background-color: rgb(0, 29, 49); + border-radius: 2rem; + } + } +} + +@include breakpoints.from1024() { + .side-menu-nav { + &__logo-container { + display: block; + + margin: 0 auto; + } + + &__icons { + display: none; + } + + &__exitmenu { + display: none; + } + } + + .side-menu-content { + width: 70%; + + &__menuitens { + transition: 0.3s; + + &:hover { + opacity: 0.6; + } + } + } +} 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 0dbdbfc8..623d7abc 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -21,8 +21,8 @@ $success200: rgb(49, 138, 94); // error $error100: rgb(179, 38, 30); -// 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); @@ -30,7 +30,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); @@ -45,4 +45,6 @@ $shade100: rgb(0, 0, 0); // custom +$blue100: rgb(209, 230, 255); +$blue150: rgba(209, 230, 255, 0.5); $blue600: rgb(18, 104, 204);