Show carousel of images when multiple images are available#14337
Show carousel of images when multiple images are available#14337chahmedejaz wants to merge 16 commits into
Conversation
…ail navigation and use it in admin product preview
|
Hey @mariocarabotta - The carousel for multiple images on the product has been deployed to FR Staging. Please review. But of course, you may create your own and play around. Thanks. P.S. Should we introduce a feature flag for this feature, or deploy it all at once? |
|
nice work!! some visual changes below (sorry pretty rough notes, tweaked in the browser). I will come back to you re feature flag, just checking with Rachel. active image container (.ofn-thumbnail-carousel__image) arrow circle arrows caption ofn-thumbnail-carousel__thumbs ng-scope |
|
@mariocarabotta @chahmedejaz I don't see an advantage to use a feature flag here. However then let's move this PR through testing with testers as well, so we check if there aren't any edge case. We probably need to update the acceptance criteria:
Is there a max. of pictures we can handle? |
this issue will need to be released together with this one, so that people don't see any carousel in production, but we are still able to see and test it in staging. we'll also need to give a heads up to the testers so that they have some test accounts in staging with multiple images. once we hide the button to add, they won't be able to include additional images to a product.
updated to reflect how it currently behaves (from the oldest upload to the most recent). I think it's ok like this but please let me know if you think otherwise.
mmm it's a bit tricky because what we are going to be testing now is a product with multiple product images, but what we are going to enable for users in production eventually is a product with one product image and one image per variant. In my mind this issue is really building the carousel frontend and testing that it can show multiple images, as a UI element.
I have been back and forth on specifying this. The max of images would be the same as the max of variants that a product can have. is there a limit to how many variants we can have per product? In terms of how it renders a considerable number of images in the carousel, we'll need to fix it. Here's an example. I would prefer if images would just wrap, but I am also aware that some producers have 20 variants per product. But maybe they would not upload a photo for each one. I am leaning towards wrapping rather than a side scrolling which is not particularly accessible. Thoughts? |
|
thanks Mario.
oh I didn't know about this dependency. It sounds to me more complex then than introducing a feature toggle now and releasing carousel only when the multiple image upload is available. Let's see what @chahmedejaz thinks. If we introduce a feature toggle now; we can leave the carousel hidden and we avoid the work on #14157
in that case, and again if we have a feature toggle, the easiest here is to merge without testing manually (the feature is hidden, therefore no problem). And wait for a complete test when the image upload is ready. Aligned on wrapping (but i don't think your example link is working?) |
|
Thanks for the feedback, @mariocarabotta - I’ll address this and follow up. @RachL, @mariocarabotta For this case, I think we have two options:
I’m leaning toward the second option since the additional work for the related issue seems relatively small, and it would allow us to ship the carousel changes in a more complete state. |
|
given @chahmedejaz comment, how about we go for his suggested option 2? |
|
Let's go for option 2 then! Let's just keep in mind that sometimes the follow-up PR takes more time, and maintaining the previous one can be a pain. |
|
Adding a note here as well:
|
|
Thanks for catching this, @mariocarabotta! I missed it as well since I didn't have any products with long descriptions. 😅 Carousel.demo.mp4 |
|
looking great @chahmedejaz, now it's fixed! amazing work and thank you for your patience |
| position: absolute; | ||
| top: 50%; | ||
| transform: translateY(-50%); | ||
| z-index: 2; |
There was a problem hiding this comment.
I think it's would be good to use a variables here, and defined it in a central place with other z-index value like what we started doing for the admin CSS :
openfoodnetwork/app/webpacker/css/admin/globals/variables.scss
Lines 185 to 192 in 11e3b90
There was a problem hiding this comment.
It looks like you copied the CSS from the frontend, ideally we would share the same CSS so any changes to the frontend flows to the product preview. Could you have a look at sharing the CSS ?
|
|
||
| @import 'modal_component/modal_component'; | ||
| @import 'carousel_component/carousel_component'; | ||
| @import 'thumbnail_carousel_component/thumbnail_carousel_component'; |
There was a problem hiding this comment.
I don't see any CSS for thumbnail_carousel_component' is this needed ?
There was a problem hiding this comment.
The CSS file exists at app/components/thumbnail_carousel_component/thumbnail_carousel_component.scss and is imported in both darkswarm/all.scss and admin/all.scss.
| @@ -0,0 +1,30 @@ | |||
| # frozen_string_literal: true | |||
|
|
|||
| RSpec.describe CarouselComponent, type: :component do | |||
| $scope.productPropertySelectors = FilterSelectorsService.createSelectors() | ||
|
|
||
| $scope.currentProductImageIndex = 0 | ||
| $scope._productCarouselImages = null |
There was a problem hiding this comment.
Why the underscore in the name here ?
There was a problem hiding this comment.
The leading underscore is intentional. It's being used to indicate that this is an internal/private variable rather than a public one. The naming is purely for annotation and readability purposes.
| margin-left: 16px; | ||
| border-radius: 8px 8px 0px 0px; | ||
| font-family: Roboto, Arial, sans-serif; | ||
| font-weight: 600; | ||
| font-size: 0.9rem; | ||
| padding: 8px 12px 4px 12px; | ||
| color: #444; |
There was a problem hiding this comment.
I think in general it would be good to use SCSS variables when applicable,could you could review the final CSS and add variables ?
| .ofn-thumbnail-carousel | ||
| .swiper-wrapper{"ng-style" => "{'transform': 'translateX(-' + (currentProductImageIndex * 100) + '%)', 'transition': 'transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1)'}"} | ||
| .swiper-slide{"ng-repeat" => "image in productCarouselImages() track by $index"} | ||
| %img.ofn-thumbnail-carousel__image{"ng-src" => "{{image.url}}", "ng-attr-alt" => "{{image.alt}}"} | ||
| %p.ofn-thumbnail-carousel__caption{"ng-if" => "image.caption", "ng-bind" => "image.caption"} | ||
|
|
||
| .swiper-slide{"ng-if" => "productCarouselImages().length === 0"} | ||
| %img.ofn-thumbnail-carousel__image.placeholder{ src: Spree::Image.default_image_url(:large), "alt" => "Product image placeholder" } | ||
|
|
||
| %button.ofn-thumbnail-carousel__button.ofn-thumbnail-carousel__button--prev{ | ||
| type: "button", | ||
| "ng-if" => "productCarouselImages().length > 1", | ||
| "ng-click" => "previousProductImage()", | ||
| "aria-label" => "Previous slide" | ||
| } | ||
| %span.ofn-thumbnail-carousel__button-icon{ "aria-hidden" => "true" } ❮ | ||
|
|
||
| %button.ofn-thumbnail-carousel__button.ofn-thumbnail-carousel__button--next{ | ||
| type: "button", | ||
| "ng-if" => "productCarouselImages().length > 1", | ||
| "ng-click" => "nextProductImage()", | ||
| "aria-label" => "Next slide" | ||
| } | ||
| %span.ofn-thumbnail-carousel__button-icon{ "aria-hidden" => "true" } ❯ | ||
|
|
||
| .ofn-thumbnail-carousel__thumbs{"ng-if" => "productCarouselImages().length > 1"} | ||
| %button.ofn-thumbnail-carousel__thumb{ | ||
| type: "button", | ||
| "ng-repeat" => "image in productCarouselImages() track by $index", | ||
| "ng-click" => "selectProductImage($index)", | ||
| "ng-class" => "{'is-active': $index === currentProductImageIndex}", | ||
| "ng-attr-aria-label" => "Go to slide {{$index + 1}}", | ||
| "ng-attr-aria-pressed" => "{{$index === currentProductImageIndex}}" | ||
| } | ||
| %img.ofn-thumbnail-carousel__thumb-image{"ng-src" => "{{image.thumb_url || image.url}}", "ng-attr-alt" => "{{image.alt}}"} |
There was a problem hiding this comment.
Looking at this and some of the other later commit, it looks like you had to re implement the Carousel functionality in AngularJS. I'd prefer if we could void that, because now we would have two places to look at any time we need to make changes to the carousel. And from what I can see you had to re implement some of the swipping functionality we get out of the box the Stimulus carousel.
I would much rather we rewrite the modal using StimulusJS and use the same carousel everywhere. I am not sure that's easily feasible, but I think it's worth investigating.
This PR sort of introduce the StimulusJS product modal ( I just reused the one we use for the product preview in the backoffice. ). The modal is included the html, but we could probably do something similar to what is done in the backoffice, load the modal via an AJAX call.
@openfoodfoundation/reviewers what do you think ?
There was a problem hiding this comment.
The modal is included the html, but we could probably do something similar to what is done in the backoffice, load the modal via an AJAX call.
That sounds good. We probably don't want to load all the product modals into the shop list. That would be bad for performance.
…y and consistency.
0dc2f72 to
9a9ef3d
Compare
e483f6b to
bf9ab90
Compare
bf9ab90 to
f6dccf8
Compare
rioug
left a comment
There was a problem hiding this comment.
Thanks @chahmedejaz , it's much better like this. Maybe we can slightly improve one of spec, but it's otherwise good to go !
| if (!container) return; | ||
|
|
||
| this.abortPendingRequest(); | ||
| this.abortController = new AbortController(); |
There was a problem hiding this comment.
TIL about AbortController !
| async load(url) { | ||
| if (!url) return; | ||
|
|
||
| const container = document.getElementById("shop-product-modal-container"); |
There was a problem hiding this comment.
I think ideally this would be target associated with the controller, but it's probably too tricky with our existing HMTL.
| .row | ||
| .columns.small-12.medium-6.large-6.product-header | ||
| %h3= @product.name | ||
| - if @supplier | ||
| %span | ||
| %em= t("products_from") | ||
| %span= @supplier.name | ||
|
|
||
| %br | ||
|
|
||
| .filter-shopfront.property-selectors.inline-block | ||
| %ul | ||
| - @product.properties_including_inherited.each do |property| | ||
| - next if property[:name].blank? | ||
| %li | ||
| %span= property[:name] | ||
|
|
||
| - if @product.description.present? | ||
| .product-description{ "data-controller" => "add-blank-to-link" } | ||
| %p.text-small | ||
| - # description is sanitized in Spree::Product#description method | ||
| = @product.description.html_safe | ||
|
|
||
| .columns.small-12.medium-6.large-6.product-img | ||
| = render ThumbnailCarouselComponent.new(images: @carousel_images, show_captions: true, class: (@product.images.any? ? nil : "placeholder")) |
There was a problem hiding this comment.
It would have been better to reuse the existing template from the backoffice, like what I did here : 67316ea
That said, it's going to create conflict if we both do the same change : ) Let's leave if as it is, and I'll sort it out while I work on the Product Grid view.
|
|
||
| def open_product_modal(product) | ||
| page.find("a", text: product.name).click | ||
| page.find("#product-#{product.id} [data-controller~='shop-product-modal']", |
There was a problem hiding this comment.
This would be better if it wasn't tied to the data-controller name, can't you use the product link like before ?
mkllnk
left a comment
There was a problem hiding this comment.
I have to admit that I looked at this in a rush. Generally it seems okay and Mario verified that it worked. It's a lot more code than I hoped though. I don't have any suggestions for improvements. I guess, I was just hoping that using an existing component would be much easier.
What? Why?
This PR introduces support for displaying multiple product images through a reusable carousel component.
As part of the broader multiple-images initiative, this change implements the carousel experience in:
The carousel is only displayed when a product has multiple images. Products with a single image continue to show the existing single-image experience.
To support this functionality, this PR:
This work allows us to build and validate the carousel experience ahead of the future backoffice work for managing multiple images.
What should we test?
Single image products
Multiple image products (shop)
Multiple image products (admin)
Image management
Regression testing
Release notes
Changelog Category (reviewers may add a label for the release notes):
Dependencies
Need the following PR to be merged along with this one:
Documentation updates
None.