Skip to content

Commit 4d42f13

Browse files
committed
First pass at CSS carousel with fallback
1 parent ff5acf7 commit 4d42f13

File tree

8 files changed

+222
-88
lines changed

8 files changed

+222
-88
lines changed

includes/Renderer/Stories/Carousel_Renderer.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,21 @@ public function render( array $args = [] ): string {
9898
ob_start();
9999
?>
100100
<div class="<?php echo esc_attr( $container_classes ); ?>" data-id="<?php echo esc_attr( (string) $this->instance_id ); ?>">
101-
<div class="web-stories-list__inner-wrapper <?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>" style="<?php echo esc_attr( $container_styles ); ?>">
101+
<div
102+
class="web-stories-list__inner-wrapper <?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>"
103+
style="<?php echo esc_attr( $container_styles ); ?>"
104+
>
102105
<?php
103106
if ( ! $this->context->is_amp() ) {
104107
$this->assets->enqueue_script( self::SCRIPT_HANDLE );
105108
$this->assets->enqueue_style( self::SCRIPT_HANDLE );
106109
?>
107-
<div class="web-stories-list__carousel <?php echo esc_attr( $this->get_view_type() ); ?>" data-id="<?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>">
110+
<div
111+
class="web-stories-list__carousel <?php echo esc_attr( $this->get_view_type() ); ?>"
112+
data-id="<?php echo esc_attr( 'carousel-' . $this->instance_id ); ?>"
113+
data-prev="<?php esc_attr_e( 'Previous', 'web-stories' ); ?>"
114+
data-next="<?php esc_attr_e( 'Next', 'web-stories' ); ?>"
115+
>
108116
<?php
109117
array_map(
110118
function () {

packages/stories-block/src/css/views/carousel.css

+78
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,81 @@ html[dir='rtl'] .glider-prev.disabled:hover,
135135
display: flex;
136136
overflow-y: scroll;
137137
}
138+
139+
@supports (scroll-marker-group: after) {
140+
.web-stories-list.is-carousel .web-stories-list__carousel ~ .glider-prev,
141+
.web-stories-list.is-carousel .web-stories-list__carousel ~ .glider-next {
142+
display: none;
143+
}
144+
145+
.web-stories-list.is-carousel .web-stories-list__inner-wrapper {
146+
max-width: none !important;
147+
}
148+
149+
.web-stories-list.is-carousel .web-stories-list__carousel {
150+
display: grid;
151+
grid-auto-flow: column;
152+
justify-content: start;
153+
154+
scroll-behavior: smooth;
155+
156+
scrollbar-width: none;
157+
/* stylelint-disable-next-line declaration-property-value-no-unknown */
158+
container-type: inline-size scroll-state;
159+
160+
overflow-x: auto; /* todo, why does this hide everything */
161+
overscroll-behavior-x: contain;
162+
163+
scroll-snap-type: x mandatory;
164+
165+
position: relative;
166+
}
167+
168+
/* stylelint-disable selector-type-no-unknown, selector-pseudo-element-no-unknown */
169+
170+
.web-stories-list.is-carousel .web-stories-list__carousel::scroll-button(*) {
171+
display: block;
172+
aspect-ratio: 1;
173+
border-radius: 50%;
174+
background-color: #eaeaea;
175+
background-size: 30%;
176+
background-repeat: no-repeat;
177+
background-position: center;
178+
box-sizing: border-box;
179+
height: 34px;
180+
width: 34px;
181+
border-style: none;
182+
z-index: 10;
183+
pointer-events: all;
184+
cursor: pointer;
185+
position: absolute;
186+
transform: translateY(100%);
187+
top: 0;
188+
}
189+
190+
.web-stories-list.is-carousel
191+
.web-stories-list__carousel::scroll-button(*):disabled {
192+
display: none;
193+
}
194+
195+
.web-stories-list.is-carousel
196+
.web-stories-list__carousel::scroll-button(*):hover {
197+
filter: invert(1);
198+
}
199+
200+
.web-stories-list.is-carousel
201+
.web-stories-list__carousel::scroll-button(left) {
202+
transform: translateY(100%) rotate(180deg);
203+
/* stylelint-disable-next-line declaration-property-value-no-unknown */
204+
content: url('../../inline-icons/carousel-arrow.svg') / attr(data-prev);
205+
}
206+
207+
.web-stories-list.is-carousel
208+
.web-stories-list__carousel::scroll-button(right) {
209+
/* stylelint-disable-next-line declaration-property-value-no-unknown */
210+
content: url('../../inline-icons/carousel-arrow.svg') / attr(data-next);
211+
right: 0;
212+
}
213+
214+
/* stylelint-enable selector-type-no-unknown, selector-pseudo-element-no-unknown */
215+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
declare global {
17+
let __webpack_public_path__: string;
18+
19+
interface Window {
20+
webStoriesCarouselSettings: {
21+
publicPath: string;
22+
config: {
23+
isRTL: boolean;
24+
};
25+
};
26+
}
27+
}
28+
29+
export type {};

packages/stories-carousel/src/index.js

-85
This file was deleted.
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
__webpack_public_path__ = window.webStoriesCarouselSettings.publicPath;
18+
19+
document.addEventListener('DOMContentLoaded', () => {
20+
void (async () => {
21+
// Do not initialize Glider.js if browser supports CSS carousels.
22+
if (CSS.supports('scroll-marker-group: after')) {
23+
return;
24+
}
25+
26+
const carouselWrappers: NodeListOf<HTMLElement> = document.querySelectorAll(
27+
'.web-stories-list__carousel'
28+
);
29+
30+
const isRTL =
31+
window.webStoriesCarouselSettings.config.isRTL ||
32+
'rtl' === document.documentElement.getAttribute('dir');
33+
34+
if (!carouselWrappers.length) {
35+
return;
36+
}
37+
38+
const { default: Glider } = await import(
39+
/* webpackChunkName: "chunk-web-stories-glider" */ '@web-stories-wp/glider' // @ts-expexct-error
40+
);
41+
42+
carouselWrappers.forEach((carouselWrapper: HTMLElement) => {
43+
// For multiple instance of the glider we need to link nav arrows appropriately.
44+
const carouselId = carouselWrapper.dataset.id;
45+
46+
const navArrows = !isRTL
47+
? {
48+
prev: `.${carouselId} .glider-prev`,
49+
next: `.${carouselId} .glider-next`,
50+
}
51+
: {
52+
prev: `.${carouselId} .glider-next`,
53+
next: `.${carouselId} .glider-prev`,
54+
};
55+
56+
const isCircles = carouselWrapper.classList.contains('circles');
57+
const itemStyle = window.getComputedStyle(
58+
carouselWrapper.querySelector(
59+
'.web-stories-list__story'
60+
) as unknown as HTMLElement
61+
);
62+
63+
const itemWidth =
64+
Number.parseFloat(itemStyle.width) +
65+
(Number.parseFloat(itemStyle.marginLeft) +
66+
Number.parseFloat(itemStyle.marginRight));
67+
68+
// For circles view we would want to keep it auto.
69+
if (isCircles) {
70+
/* eslint-disable-next-line no-new -- we do not store the object as no further computation required with the built object. */
71+
new Glider(carouselWrapper, {
72+
// Set to `auto` and provide item width to adjust to viewport
73+
slidesToShow: 'auto',
74+
slidesToScroll: 'auto',
75+
itemWidth,
76+
duration: 0.25,
77+
scrollLock: true,
78+
arrows: navArrows,
79+
});
80+
} else {
81+
// For Box Carousel we are showing single slide below tablets viewport.
82+
/* eslint-disable-next-line no-new -- we do not store the object as no further computation required with the built object. */
83+
new Glider(carouselWrapper, {
84+
slidesToShow: 'auto',
85+
slidesToScroll: 'auto',
86+
itemWidth,
87+
duration: 0.25,
88+
scrollLock: true,
89+
arrows: navArrows,
90+
});
91+
}
92+
});
93+
})();
94+
});
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.shared.json",
3+
"compilerOptions": {
4+
"rootDir": "src",
5+
"declarationDir": "dist-types"
6+
},
7+
"references": [],
8+
"include": ["src/**/*"]
9+
}

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
{ "path": "packages/rich-text" },
2020
{ "path": "packages/stickers" },
2121
{ "path": "packages/stories-block" },
22+
{ "path": "packages/stories-carousel" },
2223
{ "path": "packages/story-editor" },
2324
{ "path": "packages/templates" },
2425
{ "path": "packages/test-utils" },

webpack.config.cjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ const editorAndDashboard = {
394394
const webStoriesScripts = {
395395
...sharedConfig,
396396
entry: {
397-
'web-stories-carousel': './packages/stories-carousel/src/index.js',
397+
'web-stories-carousel': './packages/stories-carousel/src/index.ts',
398398
},
399399
plugins: [
400400
...sharedConfig.plugins,

0 commit comments

Comments
 (0)