Skip to content

Commit

Permalink
StreamGallery: Initial implementation (ampproject#30597)
Browse files Browse the repository at this point in the history
* Add Storybook samples

* Prototype Preact StreamGallery

* Prototype AMP component

* Allow stream gallery to import from base carousel

* Add unit tests

* Remove slideAlign for now

* Sync with latest BaseCarousel changes

* Use ResizeObserver

* Update comment note

* Shrink arrow JSX

* Do not export `Controls`

* Update tests

* Add guard in Scroller class

* Update test for third slide being rendered

* Disallow infinity maxWidth value

* Guard for ResizeObserver testing error

* Clean up logic

* Add AMP CSS and test

* Rename `width` to `containerWidth`

* Use ResizeObserver entry to measure container

* Reset window.onerror

* Remove after hook for now

* Update dependency checks

* Use ref from scroller

* Prefer ?? to ||

* Reset window.onerror

* Return the ref.current.node as the root element

* Inline JSX

* Use the ResizeObserver from the local window

* Guard for ref.current

* Move styles to classes

* getVisibleCount should return visibleCount

* Scroller node not needed

* Fix types

* Update dependency allowlist

* Resolve extraSpace class

* Destructure props in function body
  • Loading branch information
caroqliu authored and ed-bird committed Dec 10, 2020
1 parent 054b7eb commit 2a99989
Show file tree
Hide file tree
Showing 12 changed files with 886 additions and 1 deletion.
6 changes: 6 additions & 0 deletions build-system/compile/bundles.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,12 @@ exports.extensionBundles = [
options: {hasCss: true},
type: TYPES.MISC,
},
{
name: 'amp-stream-gallery',
version: '1.0',
latestVersion: '0.1',
type: TYPES.MISC,
},
{
name: 'amp-selector',
version: ['0.1', '1.0'],
Expand Down
2 changes: 2 additions & 0 deletions build-system/test-configs/dep-check-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ exports.rules = [
'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/carousel-events.js',
'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/child-layout-manager.js',
'extensions/amp-stream-gallery/0.1/amp-stream-gallery.js->extensions/amp-base-carousel/0.1/responsive-attributes.js',
'extensions/amp-stream-gallery/1.0/amp-stream-gallery.js->extensions/amp-base-carousel/1.0/base-carousel.jss.js',
'extensions/amp-stream-gallery/1.0/stream-gallery.js->extensions/amp-base-carousel/1.0/base-carousel.js',

// Facebook components
'extensions/amp-facebook-page/0.1/amp-facebook-page.js->extensions/amp-facebook/0.1/facebook-loader.js',
Expand Down
6 changes: 6 additions & 0 deletions extensions/amp-base-carousel/1.0/base-carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ function BaseCarouselWithRef(
carouselContext.setCurrentSlide ?? setCurrentSlideState;
const {setSlideCount} = carouselContext;
const scrollRef = useRef(null);
const containRef = useRef(null);
const contentRef = useRef(null);

const next = useCallback(() => scrollRef.current.next(), []);
const prev = useCallback(() => scrollRef.current.prev(), []);
Expand All @@ -95,6 +97,8 @@ function BaseCarouselWithRef(
goToSlide: (index) => setRestingIndex(index),
next,
prev,
root: containRef.current,
node: contentRef.current,
}),
[next, prev, setRestingIndex]
);
Expand Down Expand Up @@ -125,6 +129,8 @@ function BaseCarouselWithRef(
layout={true}
paint={true}
contentStyle={{display: 'flex'}}
ref={containRef}
contentRef={contentRef}
{...rest}
>
{!hideControls && (
Expand Down
4 changes: 3 additions & 1 deletion extensions/amp-base-carousel/1.0/scroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ function ScrollerWithRef(
// intermediary renders will interupt scroll and cause jank.
const updateCurrentIndex = () => {
const container = containerRef.current;

if (!container) {
return;
}
if (mixedLength) {
const acc = {index: 0, width: 0};
for (let i = 0; i < container.children.length; i++) {
Expand Down
84 changes: 84 additions & 0 deletions extensions/amp-stream-gallery/1.0/amp-stream-gallery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {CSS as CAROUSEL_CSS} from '../../amp-base-carousel/1.0/base-carousel.jss';
import {CSS as GALLERY_CSS} from './stream-gallery.jss';
import {PreactBaseElement} from '../../../src/preact/base-element';
import {StreamGallery} from './stream-gallery';
import {isExperimentOn} from '../../../src/experiments';
import {isLayoutSizeDefined} from '../../../src/layout';
import {userAssert} from '../../../src/log';

/** @const {string} */
const TAG = 'amp-stream-gallery';

class AmpStreamGallery extends PreactBaseElement {
/** @override */
isLayoutSupported(layout) {
userAssert(
isExperimentOn(this.win, 'amp-stream-gallery-bento'),
'expected amp-stream-gallery-bento experiment to be enabled'
);
return isLayoutSizeDefined(layout);
}
}

/** @override */
AmpStreamGallery['Component'] = StreamGallery;

/** @override */
AmpStreamGallery['layoutSizeDefined'] = true;

/** @override */
AmpStreamGallery['children'] = {
'arrowPrev': {
name: 'arrowPrev',
selector: '[slot="prev-arrow"]',
single: true,
},
'arrowNext': {
name: 'arrowNext',
selector: '[slot="next-arrow"]',
single: true,
},
'children': {
name: 'children',
selector: '*', // This should be last as catch-all.
single: false,
},
};

/** @override */
AmpStreamGallery['props'] = {
'extraSpace': {attr: 'extra-space', type: 'string'},
'insetArrowVisibility': {attr: 'inset-arrow-visibility', type: 'string'},
'loop': {attr: 'loop', type: 'boolean'},
'minItemWidth': {attr: 'min-item-width', type: 'number'},
'maxItemWidth': {attr: 'max-item-width', type: 'number'},
'maxVisibleCount': {attr: 'max-visible-count', type: 'number'},
'minVisibleCount': {attr: 'min-visible-count', type: 'number'},
'outsetArrows': {attr: 'outset-arrows', type: 'boolean'},
'peek': {attr: 'peek', type: 'number'},
'slideAlign': {attr: 'slide-align', type: 'string'},
'snap': {attr: 'snap', type: 'boolean'},
};

/** @override */
AmpStreamGallery['shadowCss'] = GALLERY_CSS + CAROUSEL_CSS;

AMP.extension(TAG, '1.0', (AMP) => {
AMP.registerElement(TAG, AmpStreamGallery);
});
90 changes: 90 additions & 0 deletions extensions/amp-stream-gallery/1.0/storybook/Basic.amp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as Preact from '../../../../src/preact';
import {boolean, number, select, withKnobs} from '@storybook/addon-knobs';
import {withA11y} from '@storybook/addon-a11y';
import {withAmp} from '@ampproject/storybook-addon';

const INSET_ARROW_VISIBILITY = ['auto', 'always', 'never'];

export default {
title: 'amp-stream-gallery-1_0',
decorators: [withKnobs, withA11y, withAmp],

parameters: {
extensions: [{name: 'amp-stream-gallery', version: '1.0'}],

experiments: ['amp-stream-gallery-bento', 'amp-stream-carousel-bento'],
},
};

export const Default = () => {
const slideCount = number('slide count', 15, {min: 3, max: 99});
const extraSpace = boolean('extra space around?', true);
const insetArrowVisibility = select(
'inset arrow visibility',
INSET_ARROW_VISIBILITY
);
const loop = boolean('loop', true);
const snap = boolean('snap', true);
const slideAlign = select('slide align', ['start', 'center']);
const minItemWidth = number('min item width', 130, {min: 1});
const maxItemWidth = number('max item width', 180, {min: 1});
const minVisibleCount = number('min visible count', 2.5, {min: 1});
const maxVisibleCount = number('max visible count', 5, {min: 1});
const peek = number('peek', 0, {min: 1});
const outsetArrows = boolean('outset arrows', true);
const colorIncrement = Math.floor(255 / (slideCount + 1));
return (
<amp-stream-gallery
width="735"
height="225"
layout="responsive"
extra-space={extraSpace}
inset-arrow-visibility={insetArrowVisibility}
loop={loop}
min-item-width={minItemWidth}
max-item-width={maxItemWidth}
min-visible-count={minVisibleCount}
max-visible-count={maxVisibleCount}
outset-arrows={outsetArrows}
peek={peek}
slide-align={slideAlign}
snap={snap}
>
{Array.from({length: slideCount}, (x, i) => {
const v = colorIncrement * (i + 1);
return (
<amp-layout width="245" height="225" layout="flex-item">
<svg viewBox="0 0 440 225">
<rect
style={{fill: `rgb(${v}, 100, 100)`}}
width="440"
height="225"
/>
Sorry, your browser does not support inline SVG.
</svg>
</amp-layout>
);
})}
</amp-stream-gallery>
);
};

Default.story = {
name: 'default',
};
80 changes: 80 additions & 0 deletions extensions/amp-stream-gallery/1.0/storybook/Basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as Preact from '../../../../src/preact';
import {StreamGallery} from '../stream-gallery';
import {boolean, number, select, withKnobs} from '@storybook/addon-knobs';
import {withA11y} from '@storybook/addon-a11y';

const INSET_ARROW_VISIBILITY = ['auto', 'always', 'never'];

export default {
title: 'StreamGallery',
component: StreamGallery,
decorators: [withA11y, withKnobs],
};

export const _default = () => {
const width = number('width', 735);
const height = number('height', 225);
const slideCount = number('slide count', 5, {min: 0, max: 99});
const extraSpace = boolean('extra space around?', true);
const insetArrowVisibility = select(
'inset arrow visibility',
INSET_ARROW_VISIBILITY
);
const loop = boolean('loop', true);
const snap = boolean('snap', true);
const slideAlign = select('slide align', ['start', 'center']);
const minItemWidth = number('min item width', 130, {min: 1});
const maxItemWidth = number('max item width', 180, {min: 1});
const minVisibleCount = number('min visible count', 3, {min: 1});
const maxVisibleCount = number('max visible count', 5, {min: 1});
const peek = number('peek', 0, {min: 1});
const outsetArrows = boolean('outset arrows', true);
const colorIncrement = Math.floor(255 / (slideCount + 1));
return (
<>
<StreamGallery
extraSpace={extraSpace ? 'around' : ''}
insetArrowVisibility={insetArrowVisibility}
loop={loop}
slideAlign={slideAlign}
snap={snap}
outsetArrows={outsetArrows}
minItemWidth={minItemWidth}
maxItemWidth={maxItemWidth}
minVisibleCount={minVisibleCount}
maxVisibleCount={maxVisibleCount}
peek={peek}
style={{width, height}}
>
{Array.from({length: slideCount}, (_, i) => {
const v = colorIncrement * (i + 1);
return (
<div
style={{
backgroundColor: `rgb(${v}, 100, 100)`,
width,
height,
}}
></div>
);
})}
</StreamGallery>
</>
);
};
Loading

0 comments on commit 2a99989

Please sign in to comment.