From 4b051ece1b21ac2309d75bffe21af27a6c8a7a6c Mon Sep 17 00:00:00 2001 From: Carlos Lima Date: Fri, 23 Aug 2024 08:59:43 -0300 Subject: [PATCH] [PBNTR-415] Adding Star Rating form controls (#3600) **What does this PR do?** Adding Star Rating form controls **How to test?** Steps to confirm the desired behavior: 1. Go to the Form kit page 2. Scroll down to the last input (Star Rating kit) #### Checklist: - [x] **LABELS** Add a label: `enhancement`, `bug`, `improvement`, `new kit`, `deprecated`, or `breaking`. See [Changelog & Labels](https://github.com/powerhome/playbook/wiki/Changelog-&-Labels) for details. - [x] **DEPLOY** I have added the `milano` label to show I'm ready for a review. - [ ] **TESTS** I have added test coverage to my code. --- .../docs/_form_form_with_validate.html.erb | 1 + .../pb_kits/playbook/pb_star_rating/index.js | 119 ++++++++++++------ .../pb_star_rating/star_rating.html.erb | 6 +- .../playbook/pb_star_rating/star_rating.rb | 17 +++ 4 files changed, 100 insertions(+), 43 deletions(-) diff --git a/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb b/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb index e7a37c5c60..254749c364 100644 --- a/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +++ b/playbook/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb @@ -35,6 +35,7 @@ <%= form.collection_select :example_collection_select, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %> <%= form.check_box :example_checkbox, props: { text: "Example Checkbox", label: true, required: true } %> <%= form.date_picker :example_date_picker_2, props: { label: true, required: true } %> + <%= form.star_rating_field :example_star_rating, props: { variant: "interactive", label: true, required: true } %> <%= form.actions do |action| %> <%= action.submit %> diff --git a/playbook/app/pb_kits/playbook/pb_star_rating/index.js b/playbook/app/pb_kits/playbook/pb_star_rating/index.js index a8f1797919..70ffffefa2 100644 --- a/playbook/app/pb_kits/playbook/pb_star_rating/index.js +++ b/playbook/app/pb_kits/playbook/pb_star_rating/index.js @@ -1,119 +1,156 @@ -import PbEnhancedElement from "../pb_enhanced_element"; +import PbEnhancedElement from "../pb_enhanced_element" -const STAR_RATING_SELECTOR = "[data-pb-star-rating]"; -const STAR_RATING_INPUT_ID = "star-rating-input"; +const STAR_RATING_WRAPPER_SELECTOR = "[data-pb-star-rating-wrapper]" +const STAR_RATING_SELECTOR = "[data-pb-star-rating]" +const STAR_RATING_INPUT_DATA_SELECTOR = "[data-pb-star-rating-input]" export default class PbStarRating extends PbEnhancedElement { static get selector() { - return STAR_RATING_SELECTOR; + return STAR_RATING_WRAPPER_SELECTOR } connect() { - this.element.addEventListener("click", (event) => { - const clickedStarId = event.currentTarget.id; - this.updateStarColors(clickedStarId); - this.updateHiddenInputValue(clickedStarId); - }); + this.addEventListeners() + this.handleFormReset() + } + + addEventListeners() { + this.element.querySelectorAll(STAR_RATING_SELECTOR).forEach(star => { + star.addEventListener("click", (event) => { + const clickedStarId = event.currentTarget.id + this.updateStarColors(clickedStarId) + this.updateHiddenInputValue(clickedStarId) + this.clearFormValidation() + }) - document.querySelectorAll(STAR_RATING_SELECTOR).forEach(star => { star.addEventListener("mouseenter", (event) => { const hoveredStarId = event.currentTarget.id - this.updateStarHoverColors(hoveredStarId); + this.updateStarHoverColors(hoveredStarId) }) star.addEventListener("mouseleave", () => { - this.removeStarHoverColors(); + this.removeStarHoverColors() }) star.addEventListener("keydown", (event) => { if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - this.handleStarClick(star.id); + event.preventDefault() + this.handleStarClick(star.id) } }) }) } handleStarClick(starId) { - this.updateStarColors(starId); - this.updateHiddenInputValue(starId); + this.updateStarColors(starId) + this.updateHiddenInputValue(starId) } updateStarColors(clickedStarId) { - const allStars = document.querySelectorAll(STAR_RATING_SELECTOR); + const allStars = this.element.querySelectorAll(STAR_RATING_SELECTOR) allStars.forEach(star => { - const starId = star.id; - const icon = star.querySelector(".interactive-star-icon"); + const starId = star.id + const icon = star.querySelector(".interactive-star-icon") if (icon) { if (starId <= clickedStarId) { if (star.classList.contains("yellow_star")) { - icon.classList.add("yellow-star-selected"); + icon.classList.add("yellow-star-selected") } else if (star.classList.contains("primary_star_light")) { - icon.classList.add("primary-star-selected"); + icon.classList.add("primary-star-selected") } else if (star.classList.contains("primary_star_dark")) { - icon.classList.add("primary-star-selected"); + icon.classList.add("primary-star-selected") } else if (star.classList.contains("subtle_star_light")) { - icon.classList.add("subtle-star-selected"); + icon.classList.add("subtle-star-selected") } else if (star.classList.contains("subtle_star_dark")) { - icon.classList.add("subtle-star-selected"); + icon.classList.add("subtle-star-selected") } else { - icon.classList.add("yellow-star-selected"); + icon.classList.add("yellow-star-selected") } } else { - icon.classList.remove("yellow-star-selected", "primary-star-selected", "subtle-star-selected"); + icon.classList.remove("yellow-star-selected", "primary-star-selected", "subtle-star-selected") } - icon.classList.remove("star-hovered"); + icon.classList.remove("star-hovered") } - }); + }) } updateHiddenInputValue(value) { - const hiddenInput = document.getElementById(STAR_RATING_INPUT_ID); + const hiddenInput = this.element.querySelector(STAR_RATING_INPUT_DATA_SELECTOR) if (hiddenInput) { - hiddenInput.value = value; + hiddenInput.value = value } } updateStarHoverColors(hoveredStarId) { - const allStars = document.querySelectorAll(STAR_RATING_SELECTOR); + const allStars = this.element.querySelectorAll(STAR_RATING_SELECTOR) allStars.forEach(star => { - const starId = star.id; - const icon = star.querySelector(".interactive-star-icon"); + const starId = star.id + const icon = star.querySelector(".interactive-star-icon") if (icon) { if (starId <= hoveredStarId) { if (!icon.classList.contains("yellow-star-selected") && !icon.classList.contains("primary-star-selected") && !icon.classList.contains("subtle-star-selected")) { - icon.classList.add("star-hovered"); + icon.classList.add("star-hovered") } } else { - icon.classList.remove("star-hovered"); + icon.classList.remove("star-hovered") } } - }); + }) } removeStarHoverColors() { - const allStars = document.querySelectorAll(STAR_RATING_SELECTOR); + const allStars = this.element.querySelectorAll(STAR_RATING_SELECTOR) allStars.forEach(star => { - const icon = star.querySelector(".interactive-star-icon"); + const icon = star.querySelector(".interactive-star-icon") if (icon) { if (!icon.classList.contains("yellow-star-selected") && !icon.classList.contains("primary-star-selected") && !icon.classList.contains("subtle-star-selected")) { - icon.classList.remove("star-hovered"); + icon.classList.remove("star-hovered") } } - }); + }) } isStarSelected() { - return document.querySelectorAll(".yellow-star-selected, .primary-star-selected, .subtle-star-selected").length > 0; + return this.element.querySelectorAll(".yellow-star-selected, .primary-star-selected, .subtle-star-selected").length > 0 + } + + handleFormReset() { + const form = this.element.closest("form") + if (form) { + form.addEventListener("reset", () => { + this.updateHiddenInputValue("") + this.resetStarRatingValues() + }) + } + } + + resetStarRatingValues() { + const allStars = this.element.querySelectorAll(STAR_RATING_SELECTOR) + allStars.forEach(star => { + const icon = star.querySelector(".interactive-star-icon") + if (icon) { + icon.classList.remove("yellow-star-selected", "primary-star-selected", "subtle-star-selected") + } + }) + } + + clearFormValidation() { + const hiddenInput = this.element.querySelector(STAR_RATING_INPUT_DATA_SELECTOR) + if (hiddenInput.checkValidity()) { + const errorLabelElement = this.element.querySelector(".pb_body_kit_negative") + if (errorLabelElement) { + errorLabelElement.remove() + } + } } } diff --git a/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb b/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb index 8f4fa9f626..8e4f2bb8cc 100644 --- a/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb +++ b/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.html.erb @@ -39,11 +39,13 @@ <% end %> <% else %> - <%= pb_rails("flex", props: { orientation: "column" }) do %> + <%= pb_rails("flex", props: { data: {"pb-star-rating-wrapper": "true" }, orientation: "column" }) do %> <% if object.label.present? %> <%= pb_rails("caption", props: {text: object.label, margin_bottom:"xs"}) %> <% end %> - + + <%= hidden_input_tag %> + <%= pb_rails("flex", props: { orientation: "row" }) do %> <% object.denominator.times do |index| %>
diff --git a/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.rb b/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.rb index bd82905750..50c3b76539 100644 --- a/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.rb +++ b/playbook/app/pb_kits/playbook/pb_star_rating/star_rating.rb @@ -30,6 +30,10 @@ class StarRating < Playbook::KitBase default: "display" prop :label, type: Playbook::Props::String prop :name, type: Playbook::Props::String + prop :required, type: Playbook::Props::Boolean, + default: false + prop :input_options, type: Playbook::Props::HashProp, + default: {} def one_decimal_rating rating.to_f.round(1) @@ -106,6 +110,19 @@ def star_svg_path def classname generate_classname("pb_star_rating_kit") end + + def hidden_input_tag + tag(:input, all_input_options) + end + + def all_input_options + input_options.merge( + data: { "pb-star-rating-input": true }, + name: name, + required: required, + style: "display: none" + ) + end end end end