diff --git a/src/components/SizeSelector/images/large.js b/src/components/SizeSelector/images/large.js new file mode 100644 index 00000000..7f3abc6d --- /dev/null +++ b/src/components/SizeSelector/images/large.js @@ -0,0 +1,5 @@ +const large = ` + + `; + +export default large; diff --git a/src/components/SizeSelector/images/medium.js b/src/components/SizeSelector/images/medium.js new file mode 100644 index 00000000..95ef5f51 --- /dev/null +++ b/src/components/SizeSelector/images/medium.js @@ -0,0 +1,5 @@ +const medium = ` + + `; + +export default medium; diff --git a/src/components/SizeSelector/images/small.js b/src/components/SizeSelector/images/small.js new file mode 100644 index 00000000..a08478b4 --- /dev/null +++ b/src/components/SizeSelector/images/small.js @@ -0,0 +1,5 @@ +const small = ` + +`; + +export default small; diff --git a/src/components/SizeSelector/index.js b/src/components/SizeSelector/index.js new file mode 100644 index 00000000..e79021df --- /dev/null +++ b/src/components/SizeSelector/index.js @@ -0,0 +1,147 @@ +import { Component } from 'pet-dex-utilities'; +import small from './images/small'; +import medium from './images/medium'; +import large from './images/large'; + +import './index.scss'; + +const events = ['size:change']; + +const html = ` +
+ +
+`; + +export default function SizeSelector() { + Component.call(this, { html, events }); + + this.$sizeList = this.selected.get('size-list'); + this.$cards = this.$sizeList.querySelectorAll( + '.container-size-selector__card', + ); + this.$initialCard = this.selected.get('initial-card'); + + this.listen('mount', () => { + requestAnimationFrame(() => { + this.$initialCard.scrollIntoView({ + inline: 'center', + behavior: 'instant', + }); + }); + }); + + this.$cards.forEach((item, index) => { + item.addEventListener('click', () => { + this.setScroll(item); + this.scrollEnd(item); + this.emitCardEvent('click', item, index); + }); + + item.addEventListener('keydown', (event) => { + this.handleKeyDown(event, item); + }); + }); +} + +SizeSelector.prototype = Object.assign( + SizeSelector.prototype, + Component.prototype, + { + nextElement(next) { + let nextIndex; + if (next) { + nextIndex = Array.from(this.$cards).indexOf(next); + this.scrollEnd(next); + this.emitCardEvent('keydown', next, nextIndex); + } + }, + + handleKeyDown(event, card) { + if (event.key === 'ArrowRight') { + this.nextElement(card.nextElementSibling); + } + if (event.key === 'ArrowLeft') { + this.nextElement(card.previousElementSibling); + } + }, + + setScroll(card) { + card.scrollIntoView({ inline: 'center' }); + }, + + scrollEnd(card) { + this.$sizeList.addEventListener('scrollend', () => { + this.setActiveCard(card); + card.focus(); + }); + }, + + setActiveCard(element) { + this.$cards.forEach((card) => { + card.setAttribute('aria-checked', 'false'); + card.classList.remove('container-size-selector__card--active'); + }); + element.classList.add('container-size-selector__card--active'); + element.setAttribute('aria-checked', 'true'); + }, + + emitCardEvent(eventName, card, index) { + if (eventName === 'click') { + this.emit('size:change', card, index); + } + if (eventName === 'keydown') { + this.emit('size:change', card, index); + } + }, + + activeCardInit() { + const $activeCard = Array.from(this.$cards).find((element) => + element.classList.contains('container-size-selector__card--active'), + ); + const indexCard = Array.from(this.$cards).findIndex((element) => + element.classList.contains('container-size-selector__card--active'), + ); + return { + card: $activeCard, + index: indexCard, + }; + }, + }, +); diff --git a/src/components/SizeSelector/index.scss b/src/components/SizeSelector/index.scss new file mode 100644 index 00000000..612bbd00 --- /dev/null +++ b/src/components/SizeSelector/index.scss @@ -0,0 +1,192 @@ +@use '~styles/colors.scss' as colors; +@use '~styles/breakpoints.scss' as breakpoints; +@use '~styles/fonts.scss' as fonts; + +.container-size-selector { + width: 100%; + max-width: 15rem; + + display: flex; + + justify-content: center; + + &__size-list { + max-width: 100%; + overflow-x: hidden; + + display: flex; + flex-direction: row; + flex-shrink: 0; + gap: 2.5rem; + + padding: 5rem 10rem; + + scroll-behavior: smooth; + scroll-snap-type: x mandatory; + } + + &__card:focus { + outline: none; + } + + &__card { + display: flex; + + align-items: center; + justify-content: center; + + padding: 1.5rem 0.3rem; + + box-sizing: border-box; + + background-color: colors.$secondary100; + box-shadow: 0 0 0.3rem 0.3rem colors.$gray150; + outline: 0.3rem solid transparent; + border-radius: 1.8rem; + + transition: 0.3s ease; + + cursor: pointer; + scroll-snap-align: center; + + &:focus { + outline: 0.3rem solid colors.$primary200; + } + + &:hover { + outline: 0.3rem solid colors.$primary200; + + .container-size-selector__svg path { + fill: colors.$primary200; + } + + .container-size-selector__title { + color: colors.$primary200; + } + + .container-size-selector__text { + color: colors.$primary200; + } + + .container-size-selector__container-img { + background-color: rgb(209, 230, 255); + } + } + + &--active { + transform: scale(1.3); + outline: 0.3rem solid colors.$primary200; + + .container-size-selector__svg path { + fill: colors.$primary200; + } + + .container-size-selector__title { + color: colors.$primary200; + font-size: fonts.$xs; + } + + .container-size-selector__text { + color: colors.$primary200; + font-size: fonts.$xxs; + } + + .container-size-selector__container-img { + background-color: rgb(209, 230, 255); + transform: scale(1.1); + } + } + } + + &__card-size { + min-width: 9rem; + + display: flex; + flex-direction: column; + gap: 1.5rem; + + align-items: center; + justify-content: center; + } + + &__container-img { + width: 4rem; + height: 4rem; + + display: flex; + + align-items: center; + + justify-content: center; + + padding: 0.4rem; + + background-color: colors.$gray150; + border-radius: 100%; + + transition: 0.3s ease; + } + + &__container-text { + display: flex; + flex-direction: column; + gap: 0.3em; + + align-items: center; + justify-content: center; + + text-align: center; + } + + &__svg path { + transition: 0.3s ease; + + fill: rgb(128, 139, 154); + } + + &__title { + font-family: fonts.$primaryFont; + color: colors.$gray800; + font-size: fonts.$xs; + font-weight: fonts.$semiBold; + + transition: 0.3s ease; + } + + &__text { + font-family: fonts.$primaryFont; + color: colors.$gray600; + font-size: fonts.$xxs; + font-weight: fonts.$regular; + + transition: 0.3s ease; + } +} + +@include breakpoints.from667 { + .container-size-selector { + &__size-list { + gap: 4rem; + + padding: 5rem 20rem; + } + + &__card { + padding: 4rem 2rem; + + &--active { + .container-size-selector__title { + font-size: fonts.$sm; + } + + .container-size-selector__text { + font-size: fonts.$xs; + } + } + } + + &__text { + font-size: fonts.$xs; + } + } +} diff --git a/src/stories/SizeSelector.stories.js b/src/stories/SizeSelector.stories.js new file mode 100644 index 00000000..6a7df023 --- /dev/null +++ b/src/stories/SizeSelector.stories.js @@ -0,0 +1,18 @@ +import SizeSelector from '../components/SizeSelector'; + +export default { + title: 'Components/SizeSelector', + render: () => { + const sizeselector = new SizeSelector(); + + const $container = document.createElement('div'); + $container.style.width = '100%'; + $container.style.display = 'flex'; + $container.style.justifyContent = 'center'; + sizeselector.mount($container); + + return $container; + }, +}; + +export const Default = {}; diff --git a/src/styles/fonts.scss b/src/styles/fonts.scss index 82b3e46c..0ae8109a 100644 --- a/src/styles/fonts.scss +++ b/src/styles/fonts.scss @@ -4,6 +4,7 @@ $tertiaryFont: 'Helvetica', sans-serif; $fourthFont: 'Noto Sans', sans-serif; $fifthFont: 'Poppins', sans-serif; +$xxs: 1.2rem; $xs: 1.4rem; $sm: 1.6rem; $md: 2rem;