Edit on StackBlitz
"use client";
import {
PagerComponent,
PagerPageComponent,
PagerSwitchComponent,
PagerPrevSwitchComponent,
PagerNextSwitchComponent,
} from "@lisn.js/react";
// Don't load the default pager CSS otherwise we'd have to override quite a few
// properties. We need to set custom style for everything anyway.
// import "lisn.js/pager.css";
import styles from "./demo.module.css";
export default function Page() {
return (
<>
<div className={styles.wrapper}>
<PagerComponent
className={styles.demo}
config={{
horizontal: true,
useGestures: "touch,wheel",
alignGestureDirection: true,
preventDefault: false,
}}
>
<div className={styles.arrows}>
<PagerPrevSwitchComponent
className={styles.arrow}
data-switch="prev"
aria-label="Previous"
></PagerPrevSwitchComponent>
<PagerNextSwitchComponent
className={styles.arrow}
data-switch="next"
aria-label="Next"
></PagerNextSwitchComponent>
</div>
<div className={styles.pages}>
{/* Pages act as switches themselves so that the user can tap one
to make it active */}
<PagerPageComponent className={styles.page}>
<PagerSwitchComponent className={styles.content}>
<h1>L</h1>
<h4>Lightweight.</h4>
<ul>
<li>Vanilla TypeScript</li>
<li>Highly optimized</li>
<li>No layout thrashing</li>
</ul>
</PagerSwitchComponent>
</PagerPageComponent>
<PagerPageComponent className={styles.page}>
<PagerSwitchComponent className={styles.content}>
<h1>I</h1>
<h4>Interactive.</h4>
<ul>
<li>Powerful API</li>
<li>Multi gesture support</li>
<li>Mobile/touch ready</li>
</ul>
</PagerSwitchComponent>
</PagerPageComponent>
<PagerPageComponent className={styles.page}>
<PagerSwitchComponent className={styles.content}>
<h1>S</h1>
<h4>Simple.</h4>
<ul>
<li>Intuitive syntax</li>
<li>Consistent API</li>
<li>HTML-only mode</li>
</ul>
</PagerSwitchComponent>
</PagerPageComponent>
<PagerPageComponent className={styles.page}>
<PagerSwitchComponent className={styles.content}>
<h1>N</h1>
<h4>No-nonsense.</h4>
<ul>
<li>What says on the box</li>
<li>Sensible defaults</li>
<li>Highly customizable</li>
</ul>
</PagerSwitchComponent>
</PagerPageComponent>
</div>
</PagerComponent>
</div>
</>
);
}
/* Container */
.wrapper {
--animate-duration: 0.4s;
position: fixed;
top: 0;
left: 0;
width: 100vw;
width: 100dvw;
height: 100vh;
height: 100dvh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
/* Pager and page container */
.demo {
width: 100%;
padding: 0 3vmax;
}
.pages {
--gap: 3vmin;
--tot-p: var(--lisn-js--total-pages, 1);
--curr-p: var(--lisn-js--current-page, 1);
--vis-p: min(6, var(--tot-p));
--page-w: calc(
min(
320px,
(100% - var(--gap) * (var(--vis-p) - 1)) /
(var(--vis-p) + min(var(--tot-p) - var(--vis-p), 0.5))
)
);
max-width: calc(
var(--tot-p) * var(--page-w) + (var(--vis-p) - 1) * var(--gap)
);
margin: 0 auto;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: stretch;
justify-content: flex-start;
gap: var(--gap);
overflow: visible;
transition-duration: 0.7s;
transition-property: transform;
transform: translateX(
calc(
-1 * (var(--page-w) + var(--gap)) *
max(
0,
min(
var(--tot-p) - var(--vis-p) - 0.5,
var(--curr-p) - 1.25 - (var(--vis-p) - 1) / 2
)
)
)
);
}
/* Individual pages */
.page {
padding: 5vh 3vmin;
border-radius: 15px;
background: var(--bg-color-lighter);
flex: 0 0 var(--page-w);
height: auto;
position: relative;
transform: scale(0.92);
transition-property: transform, flex-basis;
transition-duration: 0.7s;
box-shadow: rgba(0, 0, 0, 0.2) 4px 5px 5px 0px;
}
.page .content {
display: flex;
flex-direction: column;
align-items: center;
}
.page[data-lisn-page-state="current"] {
background: var(--bg-color-lightest);
transform: scale(1);
}
/* Switches (general) */
.content,
.arrow {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none;
}
/* Prev/next arrows */
.arrows {
width: 100%;
margin-bottom: 20px;
display: flex;
justify-content: center;
gap: 10px;
}
.demo[data-lisn-current-page-is-first-enabled="true"] [data-switch="prev"],
.demo[data-lisn-current-page-is-last-enabled="true"] [data-switch="next"] {
cursor: default;
pointer-events: none;
opacity: 0.25;
}
.arrow {
background: none;
padding: 0;
margin: 10px;
width: 0;
height: 0;
border-style: solid;
opacity: 0.8;
cursor: pointer;
border-width: 13px;
border-color: transparent var(--text-color);
}
.arrow:hover {
opacity: 1;
}
.arrow[data-switch="prev"] {
border-left: none !important;
}
.arrow[data-switch="next"] {
border-right: none !important;
}
/* Number of visible pages on smaller screens */
@media (max-width: 1810px) {
.pages {
--vis-p: min(5, var(--tot-p));
}
}
@media (max-width: 1530px) {
.pages {
--vis-p: min(4, var(--tot-p));
}
}
@media (max-width: 1250px) {
.pages {
--vis-p: min(3, var(--tot-p));
}
}
@media (max-width: 970px) {
.pages {
--vis-p: min(2, var(--tot-p));
}
}
@media (max-width: 750px) {
.pages {
--vis-p: 1;
}
}
/* Page content */
.demo h1 {
margin: 0 auto;
font-size: 75px;
background-image: linear-gradient(
45deg,
var(--text-color) 42%,
var(--text-color-lighter) 50%,
var(--text-color) 58%
);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
}
.demo h4 {
font-size: clamp(20px, calc(15px + 2vw), 32px);
}
.demo ul {
padding: 0;
list-style-type: none;
}
.demo ul li {
margin: 8px 0;
}
.demo ul li::before {
content: "\2726";
display: inline-block;
font-size: 0.4em;
transform: translateY(-0.4em);
margin: 0 0.6em 0 0;
}
Edit on CodePen
document.addEventListener("DOMContentLoaded", () => {
const pager = document.getElementById("demo");
const pages = pager.querySelectorAll(".page");
const switches = pager.querySelectorAll(".switch");
const prevSwitch = pager.querySelector(".prev-switch");
const nextSwitch = pager.querySelector(".next-switch");
new LISN.widgets.Pager(pager, {
pages,
switches,
prevSwitch,
nextSwitch,
horizontal: true,
useGestures: "touch,wheel",
alignGestureDirection: true,
preventDefault: false,
});
});
<div id="demo">
<div class="prev-next-buttons">
<button class="prev-switch" aria-label="Previous"></button>
<button class="next-switch" aria-label="Next"></button>
</div>
<div class="pages">
<div class="page switch">
<h1>L</h1>
<h4>Lightweight.</h4>
<ul>
<li>Vanilla TypeScript</li>
<li>Highly optimized</li>
<li>No layout thrashing</li>
</ul>
</div>
<div class="page switch">
<h1>I</h1>
<h4>Interactive.</h4>
<ul>
<li>Powerful API</li>
<li>Multi gesture support</li>
<li>Mobile/touch ready</li>
</ul>
</div>
<div class="page switch">
<h1>S</h1>
<h4>Simple.</h4>
<ul>
<li>Intuitive syntax</li>
<li>Consistent API</li>
<li>HTML-only mode</li>
</ul>
</div>
<div class="page switch">
<h1>N</h1>
<h4>No-nonsense.</h4>
<ul>
<li>What says on the box</li>
<li>Sensible defaults</li>
<li>Highly customizable</li>
</ul>
</div>
</div>
</div>
/* Pager and page container */
#demo {
width: 100%;
padding: 0 3vmax;
}
#demo .pages {
--gap: 3vmin;
--tot-p: var(--lisn-js--total-pages, 1);
--curr-p: var(--lisn-js--current-page, 1);
--vis-p: min(6, var(--tot-p));
--page-w: calc(
min(
320px,
(100% - var(--gap) * (var(--vis-p) - 1)) /
(var(--vis-p) + min(var(--tot-p) - var(--vis-p), 0.5))
)
);
max-width: calc(
var(--tot-p) * var(--page-w) + (var(--vis-p) - 1) * var(--gap)
);
margin: 0 auto;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: stretch;
justify-content: flex-start;
gap: var(--gap);
overflow: visible;
transition-duration: 0.7s;
transition-property: transform;
transform: translateX(
calc(
-1 * (var(--page-w) + var(--gap)) *
max(
0,
min(
var(--tot-p) - var(--vis-p) - 0.5,
var(--curr-p) - 1.25 - (var(--vis-p) - 1) / 2
)
)
)
);
}
/* Individual pages */
#demo .page {
padding: 5vh 3vmin;
border-radius: 15px;
background: var(--bg-color-lighter);
flex: 0 0 var(--page-w);
height: auto;
position: relative;
transform: scale(0.92);
display: flex;
flex-direction: column;
align-items: center;
transition-property: transform, flex-basis;
box-shadow: rgba(0, 0, 0, 0.2) 4px 5px 5px 0px;
}
#demo .page[data-lisn-page-state="current"] {
background: var(--bg-color-lightest);
transform: scale(1);
}
#demo .switch {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none;
}
/* Prev/next arrows */
#demo[data-lisn-current-page-is-first-enabled="true"] .prev-switch,
#demo[data-lisn-current-page-is-last-enabled="true"] .next-switch {
--btn-color: #777;
cursor: default;
pointer-events: none;
}
#demo .prev-next-buttons {
width: 100%;
margin-bottom: 20px;
display: flex;
justify-content: center;
gap: 10px;
}
#demo .prev-next-buttons button {
--btn-color: #ddd;
background: none;
padding: 0;
margin: 10px;
width: 0;
height: 0;
border-style: solid;
opacity: 0.8;
cursor: pointer;
border-width: 13px;
border-color: transparent var(--btn-color);
}
#demo .prev-next-buttons button:hover {
--btn-color: #fff;
}
#demo .prev-switch {
border-left: none !important;
}
#demo .next-switch {
border-right: none !important;
}
/* Number of visible pages on smaller screens */
@media (max-width: 1810px) {
#demo .pages {
--vis-p: min(5, var(--tot-p));
}
}
@media (max-width: 1530px) {
#demo .pages {
--vis-p: min(4, var(--tot-p));
}
}
@media (max-width: 1250px) {
#demo .pages {
--vis-p: min(3, var(--tot-p));
}
}
@media (max-width: 970px) {
#demo .pages {
--vis-p: min(2, var(--tot-p));
}
}
@media (max-width: 750px) {
#demo .pages {
--vis-p: 1;
}
}
/* Page content */
#demo h1 {
margin: 0 auto;
font-size: 75px;
background-image: linear-gradient(
45deg,
var(--text-color) 42%,
var(--text-color-lighter) 50%,
var(--text-color) 58%
);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
}
#demo h4 {
font-size: clamp(20px, calc(15px + 2vw), 32px);
}
#demo ul {
padding: 0;
list-style-type: none;
}
#demo ul li {
margin: 8px 0;
}
#demo ul li::before {
content: "\2726";
display: inline-block;
font-size: 0.4em;
transform: translateY(-0.4em);
margin: 0 0.6em 0 0;
}
Edit on CodePen
<div
id="demo"
data-lisn-pager="horizontal
| use-gestures=touch,wheel
| align-gesture-direction=true
| prevent-default=false"
>
<div class="prev-next-buttons">
<button data-lisn-pager-prev-switch aria-label="Previous"></button>
<button data-lisn-pager-next-switch aria-label="Next"></button>
</div>
<div class="pages">
<div data-lisn-pager-page data-lisn-pager-switch>
<h1>L</h1>
<h4>Lightweight.</h4>
<ul>
<li>Vanilla TypeScript</li>
<li>Highly optimized</li>
<li>No layout thrashing</li>
</ul>
</div>
<div data-lisn-pager-page data-lisn-pager-switch>
<h1>I</h1>
<h4>Interactive.</h4>
<ul>
<li>Powerful API</li>
<li>Multi gesture support</li>
<li>Mobile/touch ready</li>
</ul>
</div>
<div data-lisn-pager-page data-lisn-pager-switch>
<h1>S</h1>
<h4>Simple.</h4>
<ul>
<li>Intuitive syntax</li>
<li>Consistent API</li>
<li>HTML-only mode</li>
</ul>
</div>
<div data-lisn-pager-page data-lisn-pager-switch>
<h1>N</h1>
<h4>No-nonsense.</h4>
<ul>
<li>What says on the box</li>
<li>Sensible defaults</li>
<li>Highly customizable</li>
</ul>
</div>
</div>
</div>
/* Pager and page container */
#demo {
width: 100%;
padding: 0 3vmax;
}
#demo .pages {
--gap: 3vmin;
--tot-p: var(--lisn-js--total-pages, 1);
--curr-p: var(--lisn-js--current-page, 1);
--vis-p: min(6, var(--tot-p));
--page-w: calc(
min(
320px,
(100% - var(--gap) * (var(--vis-p) - 1)) /
(var(--vis-p) + min(var(--tot-p) - var(--vis-p), 0.5))
)
);
max-width: calc(
var(--tot-p) * var(--page-w) + (var(--vis-p) - 1) * var(--gap)
);
margin: 0 auto;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: stretch;
justify-content: flex-start;
gap: var(--gap);
overflow: visible;
transition-duration: 0.7s;
transition-property: transform;
transform: translateX(
calc(
-1 * (var(--page-w) + var(--gap)) *
max(
0,
min(
var(--tot-p) - var(--vis-p) - 0.5,
var(--curr-p) - 1.25 - (var(--vis-p) - 1) / 2
)
)
)
);
}
/* Individual pages */
#demo [data-lisn-pager-page] {
padding: 5vh 3vmin;
border-radius: 15px;
background: var(--bg-color-lighter);
flex: 0 0 var(--page-w);
height: auto;
position: relative;
transform: scale(0.92);
display: flex;
flex-direction: column;
align-items: center;
transition-property: transform, flex-basis;
box-shadow: rgba(0, 0, 0, 0.2) 4px 5px 5px 0px;
}
#demo [data-lisn-pager-page][data-lisn-page-state="current"] {
background: var(--bg-color-lightest);
transform: scale(1);
}
#demo [data-lisn-pager-switch] {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
outline: none;
}
/* Prev/next arrows */
#demo[data-lisn-current-page-is-first-enabled="true"]
[data-lisn-pager-prev-switch],
#demo[data-lisn-current-page-is-last-enabled="true"]
[data-lisn-pager-next-switch] {
--btn-color: #777;
cursor: default;
pointer-events: none;
}
#demo .prev-next-buttons {
width: 100%;
margin-bottom: 20px;
display: flex;
justify-content: center;
gap: 10px;
}
#demo .prev-next-buttons button {
--btn-color: #ddd;
background: none;
padding: 0;
margin: 10px;
width: 0;
height: 0;
border-style: solid;
opacity: 0.8;
cursor: pointer;
border-width: 13px;
border-color: transparent var(--btn-color);
}
#demo .prev-next-buttons button:hover {
--btn-color: #fff;
}
#demo [data-lisn-pager-prev-switch] {
border-left: none !important;
}
#demo [data-lisn-pager-next-switch] {
border-right: none !important;
}
/* Number of visible pages on smaller screens */
@media (max-width: 1810px) {
#demo .pages {
--vis-p: min(5, var(--tot-p));
}
}
@media (max-width: 1530px) {
#demo .pages {
--vis-p: min(4, var(--tot-p));
}
}
@media (max-width: 1250px) {
#demo .pages {
--vis-p: min(3, var(--tot-p));
}
}
@media (max-width: 970px) {
#demo .pages {
--vis-p: min(2, var(--tot-p));
}
}
@media (max-width: 750px) {
#demo .pages {
--vis-p: 1;
}
}
/* Page content */
#demo h1 {
margin: 0 auto;
font-size: 75px;
background-image: linear-gradient(
45deg,
var(--text-color) 42%,
var(--text-color-lighter) 50%,
var(--text-color) 58%
);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
}
#demo h4 {
font-size: clamp(20px, calc(15px + 2vw), 32px);
}
#demo ul {
padding: 0;
list-style-type: none;
}
#demo ul li {
margin: 8px 0;
}
#demo ul li::before {
content: "\2726";
display: inline-block;
font-size: 0.4em;
transform: translateY(-0.4em);
margin: 0 0.6em 0 0;
}