New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Bento Lightbox Gallery #32008
✨ Bento Lightbox Gallery #32008
Conversation
|
||
export const _default = () => { | ||
return ( | ||
<LightboxGallery> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think I understood your process here. And it makes sense to me. One nuance - it cannot traverse nested children. But we could enable it with making WithLightbox
public. E.g. the user code could look like this:
Somewhere once:
<LightboxGallery> // could also call it `(Auto)LightboxProvider`
{children}
</LightboxGallery>
And elsewhere:
function SomeComponent() {
return (
<div>
<WithLightbox>
<img ..../>
</WithLightbox>
</div>
);
}
This might be pretty clean from Preact point of view. Some of our components could even call WithLightbox
internally.
In AMP we could enable this somehow too:
- The
LightboxProvider
attachment is very easy for us now - all it needs to do is to propagate context. We do this today withInlineGallery
. - The
WithLightbox
would be a bit harder. But I think we can figure it out with "detached" mode. Or maybe we'd just always require related components to callWithLightbox
internally for now.
The important question here: do we currently clone DOM elements to put them inside the lightbox? Or do we reparent them? Or just position? This is important, because if we clone/reposition, then in React that'd probably be taken care of by portalling, which we haven't worked with yet here, but could open some doors for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can get nested children if we are willing to clone elements, but this is costly and somewhat hacky to drill it down like this:
<LightboxGalleryContext.Provider value={context}>
<WithDeepLightbox>{children}</WithDeepLightbox>
</LightboxGalleryContext.Provider>
function WithDeepLightbox({children}) {
return toChildArray(children).map((child) =>
child.props?.lightbox ? (
<WithLightbox>{child}</WithLightbox>
) : child.props?.children ? (
Preact.cloneElement(child, null, <WithDeepLightbox>{child.props.children}</WithDeepLightbox>)
) : (
child
)
);
}
To your point about portals - 0.1 clones and cleans lightboxable DOM elements:
amphtml/extensions/amp-lightbox-gallery/0.1/amp-lightbox-gallery.js
Lines 269 to 290 in ea2e7f6
/** | |
* Return a cleaned clone of the given element for building | |
* carousel slides with. | |
* @param {!Element} element | |
* @return {!Node} | |
* @private | |
*/ | |
cloneLightboxableElement_(element) { | |
const fallback = element.getFallback(); | |
const shouldCloneFallback = | |
element.classList.contains('amp-notsupported') && !!fallback; | |
if (shouldCloneFallback) { | |
element = fallback; | |
} | |
const deepClone = !element.classList.contains('i-amphtml-element'); | |
const clonedNode = element.cloneNode(deepClone); | |
clonedNode.removeAttribute('on'); | |
clonedNode.removeAttribute('id'); | |
clonedNode.removeAttribute('i-amphtml-layout'); | |
clonedNode.removeAttribute('fallback'); | |
return clonedNode; | |
} |
Edit: Discussed portals offline and prefer taking render
prop for inside the lightbox.
This pull request introduces 1 alert when merging 752fea6 into afde3c0 - view on LGTM.com new alerts:
|
@@ -319,6 +320,19 @@ function BaseCarouselWithRef( | |||
}} | |||
tabIndex="0" | |||
wrapperClassName={classes.carousel} | |||
contentAs={lightbox ? WithLightbox : 'div'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to graft it on top of content? Or maybe it's better to just put WithLightbox
as a child? In the later comments I was also wondering if we could just wrap the WithLightbox
around each slide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't necessarily need this, mostly wanted to reduce a layer of parenting in the tree. We can put WithLightbox
as a child for more readability, but I wonder - would the carousel have different # of intermediary layers if lightbox
is true
(+1) versus false
? And would that be weird?
@@ -357,6 +363,11 @@ function renderSlides( | |||
classes | |||
) { | |||
const {length} = children; | |||
const lightboxProps = openLightbox && { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious if it'd make sense to make WithLightbox
to wrap each slide inside for lightboxable cases? The WithLightbox
, it looks like, already implements something like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline, there are a few reasons this approach is preferred to wrapping each slide:
- Partial rendering inside
BaseCarousel
: For the portion of slides that are not rendered, they also would not be registered with theLightboxGallery
, so if it were to open, you would only see images for some of the lightbox consumers, which is not desirable for the grid view. - Slide reordering inside
BaseCarousel
: Looping slides are rendered in a different order than given. This means that if you give slidesA
,B
,C
and carousel renders them asB
,C
,A
for looping, the lightbox gallery would then also receive them inB
,C
,A
, order, even though - again for grid view - they should be registered to theLightboxGallery
in the same preserved order in which they are given to theBaseCarousel
.
For these reasons, wrapping each slide leaks implementation details.
However, this opens up the next point of investigation: Is it okay to reuse JSX? Because BaseCarousel
provides children
to WithLightbox
both as children
and render={() => children}
, we need to understand the full consequences of using them this way. i.e. What happens to ref
s?
return () => deregister(genKey); | ||
}, [genKey, deregister, register, render]); | ||
return autoLightbox ? ( | ||
<Comp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify: in this model we never need a placeholder, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what you are referring to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other words: do we ever have a UX where the original image in the document is hidden and only showing blank "gray" space?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is possible because the LightboxGallery
does not control the content of the consumer. But I am not sure why this matters?
Current approach
Original approach (Stale)
This is a significant change from 0.1 API, which has the following two foundational features:
lightbox
attribute is all that is needed to place all such labelledimg
and carousel elementsOne way to use this in AMP and conform the API is to wrap all documents with
<amp-lightbox-gallery>
, but this is not ideal as it would disrupt user supplied document structure. At the Preact layer, it's not obvious that a document-wide service is the way to go, so it's possible the composability and specificity of use could be a benefit for that environment.Alternative approaches:
useLightbox
hook inAmpContext
, but only have it work if the user importsLightboxGallery
, and not be an undo burden if they do not. Then the carousel components would support a proplightbox
which would useuseLightbox
. This would not work however for nativeimg
elements.Partial for #32759