From 9879ecbcb2d09e4245a1d1f8943f340f28a20e8b Mon Sep 17 00:00:00 2001 From: Jonas Chagas Date: Mon, 3 Jun 2024 20:04:19 -0300 Subject: [PATCH] feat: create SizeSelector component feat: add responsive chore: changed imgs to template sting fix: translation bug mobile fix: add aria-checked event feat: create new sizeSelector feat: create methods and add navigation with keyboard feat: finish component fix: remove css noPet fix: remove justify-content: center --- src/components/SizeSelector/images/large.js | 5 + src/components/SizeSelector/images/medium.js | 5 + src/components/SizeSelector/images/small.js | 5 + src/components/SizeSelector/index.js | 164 ++++++++++++++++ src/components/SizeSelector/index.scss | 190 +++++++++++++++++++ src/stories/SizeSelector.stories.js | 51 +++++ src/styles/fonts.scss | 1 + 7 files changed, 421 insertions(+) create mode 100644 src/components/SizeSelector/images/large.js create mode 100644 src/components/SizeSelector/images/medium.js create mode 100644 src/components/SizeSelector/images/small.js create mode 100644 src/components/SizeSelector/index.js create mode 100644 src/components/SizeSelector/index.scss create mode 100644 src/stories/SizeSelector.stories.js 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..1621cc76 --- /dev/null +++ b/src/components/SizeSelector/index.js @@ -0,0 +1,164 @@ +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 = ['event']; + +const html = ` +
+ +
+`; + +export default function SizeSelector() { + Component.call(this, { html, events }); + + this.$sizeList = this.selected.get('sizelist'); + this.$cards = Array.from( + this.$sizeList.querySelectorAll('.container-size-selector__card'), + ); + + const addEventListeners = (card, index) => { + card.addEventListener('click', () => { + this.selectCard(card, index); + this.getCardActiveEvent('click', card, index); + }); + + card.addEventListener('keydown', (key) => { + this.handleKeyDown(key, card); + }); + }; + + this.$cards.forEach((card, index) => addEventListeners(card, index)); +} + +SizeSelector.prototype = Object.assign( + SizeSelector.prototype, + Component.prototype, + { + setText(index, title, subtitle) { + const h3 = this.$cards[index].querySelector('h3'); + const span = this.$cards[index].querySelector('span'); + h3.textContent = title; + span.textContent = subtitle; + }, + + handleKeyDown(event, card) { + let next; + let prev; + let nextIndex; + let prevIndex; + if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { + next = card.nextElementSibling; + if (next) { + nextIndex = this.$cards.indexOf(next); + next.focus(); + this.selectCard(next, nextIndex); + this.getCardActiveEvent('keydown', next, nextIndex); + } + } + if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { + prev = card.previousElementSibling; + if (prev) { + prevIndex = this.$cards.indexOf(prev); + prev.focus(); + this.selectCard(prev, prevIndex); + this.getCardActiveEvent('keydown', prev, prevIndex); + } + } + }, + + selectCard(selected, index) { + this.addActive(selected, 'container-size-selector__card--active'); + this.centerCard(selected, index); + }, + + centerCard(card, index) { + if (index === 0) + this.$sizeList.classList.add( + 'container-size-selector__size-list--active-padding', + ); + + const cardRect = card.getBoundingClientRect(); + const listRect = this.$sizeList.getBoundingClientRect(); + const cardCenter = cardRect.left + cardRect.width / 2; + const listCenter = listRect.left + listRect.width / 2; + const offset = cardCenter - listCenter; + + this.$sizeList.scrollBy({ + left: offset, + behavior: 'smooth', + }); + }, + + addActive(element, className) { + this.$cards.forEach((card) => { + card.setAttribute('aria-checked', 'false'); + card.classList.remove(className); + }); + element.classList.add(className); + element.setAttribute('aria-checked', 'true'); + }, + + getCardActiveEvent(eventName, card, index) { + if (eventName === 'click') { + this.emit('event', card, index); + } + if (eventName === 'keydown') { + this.emit('event', card, index); + } + }, + + getCardActive() { + const $activeCard = this.$cards.find((element) => + element.classList.contains('container-size-selector__card--active'), + ); + const indexCard = 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..e9cd578c --- /dev/null +++ b/src/components/SizeSelector/index.scss @@ -0,0 +1,190 @@ +@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: 60rem; + + display: flex; + + &__size-list { + width: 100%; + overflow-x: hidden; + + display: flex; + flex-direction: row; + gap: 1rem; + + padding: 5rem 40% 5rem 0; + + transition: padding 0.4s; + + &--active-padding { + padding: 5rem 40%; + } + } + + &__card:focus { + outline: none; + } + + &__card { + display: flex; + + align-items: center; + justify-content: center; + + margin: 0 0.7rem; + + padding: 1rem 0; + + box-sizing: border-box; + + background-color: colors.$secondary100; + box-shadow: 0 0 0.3rem 0.3rem colors.$gray150; + border-radius: 1.8rem; + + transition: transform 0.3s; + + &: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; + + transition: transform 0.3s; + + .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 { + width: 4.5rem; + height: 4.5rem; + + background-color: rgb(209, 230, 255); + } + } + } + + &__card-size { + width: 9.4rem; + height: 10.6rem; + + 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; + + background-color: colors.$gray150; + border-radius: 100%; + } + + &__container-text { + display: flex; + flex-direction: column; + gap: 0.3em; + + align-items: center; + + justify-content: center; + } + + &__title { + font-family: fonts.$primaryFont; + color: colors.$gray800; + font-size: fonts.$xs; + font-weight: fonts.$semiBold; + } + + &__text { + font-family: fonts.$primaryFont; + color: colors.$gray600; + font-size: fonts.$xxs; + font-weight: fonts.$regular; + } +} + +@include breakpoints.from360 { + .container-size-selector { + &__size-list { + gap: 1.5rem; + } + + &__card { + padding: 1rem 0; + } + } +} + +@include breakpoints.from667 { + .container-size-selector { + &__size-list { + gap: 5rem; + } + + &__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..474a0df7 --- /dev/null +++ b/src/stories/SizeSelector.stories.js @@ -0,0 +1,51 @@ +import SizeSelector from '../components/SizeSelector'; + +export default { + title: 'Components/SizeSelector', + render: (args) => { + const sizeselector = new SizeSelector(); + sizeselector.setText(args.card, args.titleCard, args.subtitleCard); + sizeselector.selectCard( + sizeselector.$cards[args.selectCard], + args.selectCard, + ); + const $container = document.createElement('div'); + $container.style.width = '100%'; + $container.style.display = 'flex'; + $container.style.justifyContent = 'center'; + sizeselector.mount($container); + + return $container; + }, + argTypes: { + card: { control: 'number', default: 1 }, + titleCard: { control: 'text', default: 'Medium' }, + subtitleCard: { control: 'text', default: 'under 14kg' }, + selectCard: { control: 'number', default: 1 }, + }, +}; + +export const Default = { + args: { + card: 1, + titleCard: 'Medium', + subtitleCard: 'under 14kg', + selectCard: 1, + }, +}; + +export const CardText = { + args: { + ...Default.args, + card: 1, + titleCard: 'Medium', + subtitleCard: 'under 14kg', + }, +}; + +export const CardSelect = { + args: { + ...Default.args, + selectCard: 1, + }, +}; diff --git a/src/styles/fonts.scss b/src/styles/fonts.scss index 6c510fff..6d37f2e1 100644 --- a/src/styles/fonts.scss +++ b/src/styles/fonts.scss @@ -3,6 +3,7 @@ $secondaryFont: 'Wix Madefor Display', sans-serif; $tertiaryFont: 'Helvetica', sans-serif; $fourthFont: 'Noto Sans', sans-serif; +$xxs: 1.2rem; $xs: 1.4rem; $sm: 1.6rem; $md: 2rem;