899 lines
27 KiB
Plaintext
899 lines
27 KiB
Plaintext
// Interactive, hydration-safe content carousel.
|
|
component Carousel {
|
|
outputs {
|
|
initialize(payload: { index: number; count: number })
|
|
change(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object; reason: string | boolean })
|
|
previous(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object })
|
|
next(payload: { index: number; previousIndex: number; item: string | number | boolean | null | object })
|
|
play(payload: { index: number; interval: number })
|
|
pause(payload: { index: number })
|
|
reachStart(payload: { index: number })
|
|
reachEnd(payload: { index: number })
|
|
dragStart(payload: { index: number; x: null })
|
|
dragEnd(payload: { index: number; distance: number })
|
|
}
|
|
|
|
props {
|
|
size: string = "default"
|
|
color: string = "primary"
|
|
title: string = ""
|
|
description: string = ""
|
|
items: unknown[] = []
|
|
activeIndex: number = 0
|
|
slidesPerView: number = 1
|
|
gap: string = "0.75rem"
|
|
showPagination: boolean = false
|
|
isAutoPlay: boolean = false
|
|
autoplayInterval: number = 4000
|
|
isInfiniteLoop: boolean = false
|
|
isRTL: boolean = false
|
|
isCentered: boolean = false
|
|
isDraggable: boolean = false
|
|
isAutoHeight: boolean = false
|
|
isSnap: boolean = false
|
|
showCounter: boolean = false
|
|
thumbnails: string = "none"
|
|
ariaLabel: string = "Content carousel"
|
|
variant: string = "default"
|
|
class: string = ""
|
|
}
|
|
|
|
state currentIndex = activeIndex
|
|
state playing: boolean = false
|
|
state dragOrigin = null
|
|
state dragOffset: number = 0
|
|
state autoplayTimer = null
|
|
state carouselRoot = null
|
|
|
|
functions {
|
|
shared function slideCount() {
|
|
return items.length
|
|
}
|
|
|
|
shared function maximumIndex(count, visible) {
|
|
count = slideCount()
|
|
visible = Math.max(1, Number(slidesPerView) || 1)
|
|
if (isCentered || isSnap) {
|
|
return Math.max(0, count - 1)
|
|
}
|
|
return Math.max(0, count - visible)
|
|
}
|
|
|
|
shared function normalizedIndex(index, maximum) {
|
|
maximum = maximumIndex()
|
|
if (slideCount() === 0) {
|
|
return 0
|
|
}
|
|
if (isInfiniteLoop) {
|
|
if (index < 0) {
|
|
return maximum
|
|
}
|
|
if (index > maximum) {
|
|
return 0
|
|
}
|
|
}
|
|
return Math.max(0, Math.min(index, maximum))
|
|
}
|
|
|
|
client function rememberRoot(sourceEvent, root) {
|
|
if (!sourceEvent || !sourceEvent.currentTarget) {
|
|
return
|
|
}
|
|
root = sourceEvent.currentTarget.closest(".wrn-next--carousel")
|
|
if (root) {
|
|
carouselRoot = root
|
|
}
|
|
}
|
|
|
|
client function syncNavigation(sourceEvent, index, root, viewport, slides, slide, thumbnails, thumbnail, rail) {
|
|
rememberRoot(sourceEvent)
|
|
root = carouselRoot
|
|
if (!root) {
|
|
return
|
|
}
|
|
if (isSnap) {
|
|
viewport = root.querySelector(".wrn-next__carousel-viewport")
|
|
slides = root.querySelectorAll(".wrn-next__carousel-slide")
|
|
slide = slides[index]
|
|
if (viewport && slide) {
|
|
viewport.scrollTo({
|
|
left: slide.offsetLeft - (viewport.clientWidth - slide.clientWidth) / 2,
|
|
behavior: "smooth"
|
|
})
|
|
}
|
|
}
|
|
thumbnails = root.querySelectorAll(".wrn-next__carousel-thumbnails button")
|
|
if (thumbnails[index]) {
|
|
thumbnail = thumbnails[index]
|
|
rail = thumbnail.parentElement
|
|
rail.scrollTo({
|
|
left: thumbnail.offsetLeft - (rail.clientWidth - thumbnail.clientWidth) / 2,
|
|
top: thumbnail.offsetTop - (rail.clientHeight - thumbnail.clientHeight) / 2,
|
|
behavior: "smooth"
|
|
})
|
|
}
|
|
}
|
|
|
|
client function handleSnapScroll(sourceEvent, slides, viewport, viewportCenter, closestIndex, closestDistance, slide, slideCenter, distance, previousIndex) {
|
|
if (!isSnap) {
|
|
return
|
|
}
|
|
rememberRoot(sourceEvent)
|
|
viewport = sourceEvent.currentTarget
|
|
slides = viewport.querySelectorAll(".wrn-next__carousel-slide")
|
|
viewportCenter = viewport.scrollLeft + viewport.clientWidth / 2
|
|
closestIndex = currentIndex
|
|
closestDistance = 1000000000
|
|
slides.forEach(function (candidate, index) {
|
|
slideCenter = candidate.offsetLeft + candidate.clientWidth / 2
|
|
distance = Math.abs(slideCenter - viewportCenter)
|
|
if (distance < closestDistance) {
|
|
closestDistance = distance
|
|
closestIndex = index
|
|
}
|
|
})
|
|
closestIndex = normalizedIndex(closestIndex)
|
|
if (closestIndex !== currentIndex) {
|
|
previousIndex = currentIndex
|
|
currentIndex = closestIndex
|
|
output.change({
|
|
index: currentIndex,
|
|
previousIndex: previousIndex,
|
|
item: items[currentIndex],
|
|
reason: "snap"
|
|
})
|
|
slides = carouselRoot.querySelectorAll(".wrn-next__carousel-thumbnails button")
|
|
slide = slides[currentIndex]
|
|
if (slide) {
|
|
viewport = slide.parentElement
|
|
viewport.scrollTo({
|
|
left: slide.offsetLeft - (viewport.clientWidth - slide.clientWidth) / 2,
|
|
top: slide.offsetTop - (viewport.clientHeight - slide.clientHeight) / 2,
|
|
behavior: "smooth"
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
client function selectSlide(index, reason, sourceEvent, previousIndex) {
|
|
if (slideCount() === 0) {
|
|
return
|
|
}
|
|
previousIndex = currentIndex
|
|
currentIndex = normalizedIndex(Number(index))
|
|
syncNavigation(sourceEvent, currentIndex)
|
|
if (previousIndex === currentIndex && reason !== "initialize") {
|
|
return
|
|
}
|
|
output.change({
|
|
index: currentIndex,
|
|
previousIndex: previousIndex,
|
|
item: items[currentIndex],
|
|
reason: reason || "select"
|
|
})
|
|
if (currentIndex === 0) {
|
|
output.reachStart({ index: currentIndex })
|
|
}
|
|
if (currentIndex === maximumIndex()) {
|
|
output.reachEnd({ index: currentIndex })
|
|
}
|
|
}
|
|
|
|
client function previousSlide(sourceEvent, previousIndex) {
|
|
previousIndex = currentIndex
|
|
selectSlide(currentIndex - 1, "previous", sourceEvent)
|
|
output.previous({
|
|
index: currentIndex,
|
|
previousIndex: previousIndex,
|
|
item: items[currentIndex]
|
|
})
|
|
}
|
|
|
|
client function nextSlide(sourceEvent, previousIndex) {
|
|
previousIndex = currentIndex
|
|
selectSlide(currentIndex + 1, "next", sourceEvent)
|
|
output.next({
|
|
index: currentIndex,
|
|
previousIndex: previousIndex,
|
|
item: items[currentIndex]
|
|
})
|
|
}
|
|
|
|
client function handleKeydown(sourceEvent) {
|
|
if (sourceEvent.key === "ArrowLeft") {
|
|
sourceEvent.preventDefault()
|
|
if (isRTL) {
|
|
nextSlide(sourceEvent)
|
|
} else {
|
|
previousSlide(sourceEvent)
|
|
}
|
|
}
|
|
if (sourceEvent.key === "ArrowRight") {
|
|
sourceEvent.preventDefault()
|
|
if (isRTL) {
|
|
previousSlide(sourceEvent)
|
|
} else {
|
|
nextSlide(sourceEvent)
|
|
}
|
|
}
|
|
if (sourceEvent.key === "Home") {
|
|
sourceEvent.preventDefault()
|
|
selectSlide(0, "keyboard", sourceEvent)
|
|
}
|
|
if (sourceEvent.key === "End") {
|
|
sourceEvent.preventDefault()
|
|
selectSlide(maximumIndex(), "keyboard", sourceEvent)
|
|
}
|
|
}
|
|
|
|
client function beginDrag(sourceEvent) {
|
|
if (!isDraggable || isSnap) {
|
|
return
|
|
}
|
|
sourceEvent.preventDefault()
|
|
dragOrigin = sourceEvent.clientX
|
|
dragOffset = 0
|
|
sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId)
|
|
output.dragStart({ index: currentIndex, x: dragOrigin })
|
|
}
|
|
|
|
client function moveDrag(sourceEvent) {
|
|
if (!isDraggable || isSnap || dragOrigin === null) {
|
|
return
|
|
}
|
|
sourceEvent.preventDefault()
|
|
dragOffset = sourceEvent.clientX - dragOrigin
|
|
}
|
|
|
|
shared function cancelDrag() {
|
|
dragOrigin = null
|
|
dragOffset = 0
|
|
}
|
|
|
|
client function endDrag(sourceEvent, distance) {
|
|
if (!isDraggable || isSnap || dragOrigin === null) {
|
|
return
|
|
}
|
|
sourceEvent.preventDefault()
|
|
distance = dragOffset || sourceEvent.clientX - dragOrigin
|
|
dragOrigin = null
|
|
dragOffset = 0
|
|
if (Math.abs(distance) > 20) {
|
|
if ((distance < 0 && !isRTL) || (distance > 0 && isRTL)) {
|
|
nextSlide(sourceEvent)
|
|
} else {
|
|
previousSlide(sourceEvent)
|
|
}
|
|
}
|
|
output.dragEnd({
|
|
index: currentIndex,
|
|
distance: distance
|
|
})
|
|
}
|
|
|
|
shared function advanceAutoplay() {
|
|
if (currentIndex >= maximumIndex()) {
|
|
selectSlide(0, "autoplay")
|
|
} else {
|
|
selectSlide(currentIndex + 1, "autoplay")
|
|
}
|
|
}
|
|
|
|
client function startAutoplay() {
|
|
if (!isAutoPlay || playing || slideCount() < 2) {
|
|
return
|
|
}
|
|
playing = true
|
|
autoplayTimer = setInterval(advanceAutoplay, Math.max(1000, Number(autoplayInterval)))
|
|
output.play({ index: currentIndex, interval: autoplayInterval })
|
|
}
|
|
|
|
client function pauseAutoplay() {
|
|
if (autoplayTimer) {
|
|
clearInterval(autoplayTimer)
|
|
}
|
|
autoplayTimer = null
|
|
if (playing) {
|
|
output.pause({ index: currentIndex })
|
|
}
|
|
playing = false
|
|
}
|
|
|
|
}
|
|
|
|
lifecycle {
|
|
mount {
|
|
output.initialize({ index: currentIndex, count: slideCount() })
|
|
startAutoplay()
|
|
}
|
|
unmount {
|
|
if (autoplayTimer) {
|
|
clearInterval(autoplayTimer)
|
|
}
|
|
}
|
|
}
|
|
|
|
view {
|
|
<section
|
|
{...attrs}
|
|
class="wrn-component wrn-next--carousel wrn-component--color-{color} wrn-component--size-{size} wrn-next--variant-{variant} {class}"
|
|
data-rtl="{isRTL}"
|
|
data-centered="{isCentered}"
|
|
data-draggable="{isDraggable && !isSnap}"
|
|
data-dragging="{dragOrigin !== null}"
|
|
data-auto-height="{isAutoHeight}"
|
|
data-snap="{isSnap}"
|
|
data-thumbnails="{thumbnails}"
|
|
dir="{isRTL ? 'rtl' : 'ltr'}"
|
|
role="region"
|
|
aria-roledescription="carousel"
|
|
aria-label="{ariaLabel}"
|
|
@keydown="handleKeydown(event)"
|
|
@mouseenter="rememberRoot(event); pauseAutoplay()"
|
|
@mouseleave="startAutoplay()"
|
|
@focusin="rememberRoot(event); pauseAutoplay()"
|
|
@focusout="startAutoplay()"
|
|
>
|
|
{#if title || description}
|
|
<header class="wrn-next__carousel-header">
|
|
{#if title}<h3>{title}</h3>{/if}
|
|
{#if description}<p>{description}</p>{/if}
|
|
</header>
|
|
{/if}
|
|
|
|
<div class="wrn-next__carousel-layout">
|
|
{#if thumbnails === "vertical"}
|
|
<div class="wrn-next__carousel-thumbnails" aria-label="Choose a slide">
|
|
{#each items as item, index}
|
|
<button
|
|
type="button"
|
|
data-active="{index === currentIndex}"
|
|
aria-label="Show slide {index + 1}: {item.label || item.title}"
|
|
aria-current="{index === currentIndex ? 'true' : 'false'}"
|
|
@click="selectSlide(index, 'thumbnail', event)"
|
|
>
|
|
{#if item.thumbnail}<img src="{item.thumbnail}" alt="" />{/if}
|
|
<span>{item.label || item.title || "Slide " + (index + 1)}</span>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="wrn-next__carousel-main">
|
|
<div class="wrn-next__carousel-stage">
|
|
<div
|
|
class="wrn-next__carousel-viewport"
|
|
tabindex="0"
|
|
@scroll="handleSnapScroll(event)"
|
|
@pointerdown="beginDrag(event)"
|
|
@pointermove="moveDrag(event)"
|
|
@pointerup="endDrag(event)"
|
|
@pointercancel="cancelDrag()"
|
|
@lostpointercapture="cancelDrag()"
|
|
>
|
|
<div
|
|
class="wrn-next__carousel-track"
|
|
style="--wrn-carousel-index: {currentIndex}; --wrn-carousel-per-view: {slidesPerView}; --wrn-carousel-gap: {gap}; --wrn-carousel-drag-offset: {dragOffset}px"
|
|
>
|
|
{#each items as item, index}
|
|
<article
|
|
class="wrn-next__carousel-slide"
|
|
data-active="{index === currentIndex}"
|
|
role="group"
|
|
aria-roledescription="slide"
|
|
aria-label="{index + 1} of {items.length}"
|
|
aria-hidden="{index === currentIndex ? 'false' : 'true'}"
|
|
>
|
|
{#if item.imageSrc}
|
|
<img src="{item.imageSrc}" alt="{item.imageAlt || item.title || ''}" />
|
|
{/if}
|
|
<div class="wrn-next__carousel-slide-content">
|
|
{#if item.eyebrow}<span data-t='{item.eyebrowKey || ""}'>{item.eyebrow}</span>{/if}
|
|
{#if item.title || item.label}<h4 data-t='{item.titleKey || item.labelKey || ""}'>{item.title || item.label}</h4>{/if}
|
|
{#if item.description}<p data-t='{item.descriptionKey || ""}'>{item.description}</p>{/if}
|
|
{#if item.actionLabel}
|
|
<a href="{item.actionHref || '#'}" data-t='{item.actionLabelKey || ""}'>{item.actionLabel}</a>
|
|
{/if}
|
|
</div>
|
|
</article>
|
|
{/each}
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
class="wrn-next__carousel-control wrn-next__carousel-control--previous"
|
|
type="button"
|
|
aria-label="Previous slide"
|
|
disabled="{!isInfiniteLoop && currentIndex === 0}"
|
|
@click="previousSlide(event)"
|
|
>
|
|
<span class="icon-[lucide--chevron-left]" aria-hidden="true"></span>
|
|
</button>
|
|
<button
|
|
class="wrn-next__carousel-control wrn-next__carousel-control--next"
|
|
type="button"
|
|
aria-label="Next slide"
|
|
disabled="{!isInfiniteLoop && currentIndex === maximumIndex()}"
|
|
@click="nextSlide(event)"
|
|
>
|
|
<span class="icon-[lucide--chevron-right]" aria-hidden="true"></span>
|
|
</button>
|
|
|
|
{#if showCounter}
|
|
<output class="wrn-next__carousel-counter" aria-live="polite">
|
|
{currentIndex + 1} / {items.length}
|
|
</output>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if showPagination}
|
|
<div class="wrn-next__carousel-pagination" aria-label="Choose a slide">
|
|
{#each items as item, index}
|
|
<button
|
|
type="button"
|
|
data-active="{index === currentIndex}"
|
|
aria-label="Show slide {index + 1}"
|
|
aria-current="{index === currentIndex ? 'true' : 'false'}"
|
|
@click="selectSlide(index, 'pagination', event)"
|
|
></button>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if thumbnails === "horizontal"}
|
|
<div class="wrn-next__carousel-thumbnails" aria-label="Choose a slide">
|
|
{#each items as item, index}
|
|
<button
|
|
type="button"
|
|
data-active="{index === currentIndex}"
|
|
aria-label="Show slide {index + 1}: {item.label || item.title}"
|
|
aria-current="{index === currentIndex ? 'true' : 'false'}"
|
|
@click="selectSlide(index, 'thumbnail', event)"
|
|
>
|
|
{#if item.thumbnail}<img src="{item.thumbnail}" alt="" />{/if}
|
|
<span>{item.label || item.title || "Slide " + (index + 1)}</span>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
}
|
|
|
|
style {
|
|
.wrn-next--carousel {
|
|
--wrn-carousel-color: var(--wrn-component-color, var(--wrn-color-primary));
|
|
display: grid;
|
|
width: 100%;
|
|
min-width: 0;
|
|
gap: 1rem;
|
|
padding: 1rem;
|
|
border: 1px solid var(--wrn-color-border);
|
|
border-radius: var(--wrn-radius-md);
|
|
color: var(--wrn-color-text);
|
|
background: var(--wrn-color-surface);
|
|
box-shadow: var(--wrn-shadow-1);
|
|
}
|
|
|
|
.wrn-next__carousel-header h3,
|
|
.wrn-next__carousel-header p {
|
|
margin: 0;
|
|
}
|
|
|
|
.wrn-next__carousel-header p {
|
|
margin-top: 0.35rem;
|
|
color: var(--wrn-color-text-muted);
|
|
}
|
|
|
|
.wrn-next__carousel-layout {
|
|
display: flex;
|
|
min-width: 0;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.wrn-next__carousel-main {
|
|
display: grid;
|
|
flex: 1;
|
|
min-width: 0;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.wrn-next__carousel-stage {
|
|
position: relative;
|
|
min-width: 0;
|
|
}
|
|
|
|
.wrn-next__carousel-viewport {
|
|
position: relative;
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
border: 1px solid var(--wrn-color-border);
|
|
border-radius: var(--wrn-radius-md);
|
|
background: var(--wrn-color-surface-subtle, var(--wrn-color-surface));
|
|
outline: none;
|
|
}
|
|
|
|
.wrn-next__carousel-viewport:focus-visible {
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--wrn-carousel-color) 30%, transparent);
|
|
}
|
|
|
|
.wrn-next__carousel-track {
|
|
display: flex;
|
|
align-items: stretch;
|
|
gap: var(--wrn-carousel-gap);
|
|
transform: translateX(
|
|
calc(
|
|
var(--wrn-carousel-index) * -1 *
|
|
(
|
|
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
|
|
var(--wrn-carousel-per-view) + var(--wrn-carousel-gap)
|
|
)
|
|
)
|
|
);
|
|
transition: transform var(--wrn-motion-base) var(--wrn-ease-emphasized);
|
|
translate: var(--wrn-carousel-drag-offset, 0) 0;
|
|
will-change: transform;
|
|
}
|
|
|
|
.wrn-next--carousel[data-dragging="true"] .wrn-next__carousel-track {
|
|
transition: none;
|
|
}
|
|
|
|
.wrn-next--carousel[data-rtl="true"] .wrn-next__carousel-track {
|
|
transform: translateX(
|
|
calc(
|
|
var(--wrn-carousel-index) *
|
|
(
|
|
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
|
|
var(--wrn-carousel-per-view) + var(--wrn-carousel-gap)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
.wrn-next__carousel-slide {
|
|
position: relative;
|
|
display: grid;
|
|
flex: 0 0
|
|
calc(
|
|
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
|
|
var(--wrn-carousel-per-view)
|
|
);
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
min-height: 20rem;
|
|
place-items: center;
|
|
background: var(--wrn-color-surface-raised, var(--wrn-color-surface));
|
|
}
|
|
|
|
.wrn-next--carousel[data-auto-height="true"] .wrn-next__carousel-slide {
|
|
min-height: 0;
|
|
}
|
|
|
|
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-slide {
|
|
flex-basis: min(78%, 32rem);
|
|
}
|
|
|
|
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-track {
|
|
box-sizing: border-box;
|
|
transform: translateX(
|
|
calc(var(--wrn-carousel-index) * -1 * (min(78%, 32rem) + var(--wrn-carousel-gap)))
|
|
);
|
|
}
|
|
|
|
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-slide:first-child {
|
|
margin-inline-start: calc((100% - min(78%, 32rem)) / 2);
|
|
}
|
|
|
|
.wrn-next--carousel[data-centered="true"] .wrn-next__carousel-slide:last-child {
|
|
margin-inline-end: calc((100% - min(78%, 32rem)) / 2);
|
|
}
|
|
|
|
.wrn-next--carousel[data-centered="true"][data-rtl="true"] .wrn-next__carousel-track {
|
|
transform: translateX(
|
|
calc(var(--wrn-carousel-index) * (min(78%, 32rem) + var(--wrn-carousel-gap)))
|
|
);
|
|
}
|
|
|
|
.wrn-next__carousel-slide > img {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
inset: 0;
|
|
}
|
|
|
|
.wrn-next__carousel-slide-content {
|
|
position: relative;
|
|
z-index: 1;
|
|
width: min(100%, 38rem);
|
|
padding: clamp(2rem, 8vw, 5rem);
|
|
text-align: center;
|
|
}
|
|
|
|
.wrn-next__carousel-slide-content h4,
|
|
.wrn-next__carousel-slide-content p {
|
|
margin: 0;
|
|
}
|
|
|
|
.wrn-next__carousel-slide-content h4 {
|
|
font-size: clamp(1.5rem, 4vw, 2.4rem);
|
|
}
|
|
|
|
.wrn-next__carousel-slide-content p {
|
|
margin-top: 0.65rem;
|
|
color: var(--wrn-color-text-muted);
|
|
}
|
|
|
|
.wrn-next__carousel-control {
|
|
position: absolute;
|
|
z-index: 3;
|
|
top: 50%;
|
|
display: grid;
|
|
width: 2.75rem;
|
|
min-height: 2.75rem;
|
|
padding: 0;
|
|
border: 1px solid color-mix(in srgb, var(--wrn-color-border) 70%, transparent);
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--wrn-color-text) 76%, transparent);
|
|
color: var(--wrn-color-surface);
|
|
cursor: pointer;
|
|
place-items: center;
|
|
transform: translateY(-50%);
|
|
}
|
|
|
|
.wrn-next__carousel-control:focus-visible,
|
|
.wrn-next__carousel-pagination button:focus-visible,
|
|
.wrn-next__carousel-thumbnails button:focus-visible {
|
|
outline: 2px solid var(--wrn-carousel-color);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.wrn-next__carousel-control:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.35;
|
|
}
|
|
|
|
.wrn-next__carousel-control--previous {
|
|
left: 1rem;
|
|
}
|
|
|
|
.wrn-next__carousel-control--next {
|
|
right: 1rem;
|
|
}
|
|
|
|
.wrn-next--carousel[data-rtl="true"] .wrn-next__carousel-control--previous {
|
|
right: 1rem;
|
|
left: auto;
|
|
}
|
|
|
|
.wrn-next--carousel[data-rtl="true"] .wrn-next__carousel-control--next {
|
|
right: auto;
|
|
left: 1rem;
|
|
}
|
|
|
|
.wrn-next__carousel-pagination {
|
|
display: flex;
|
|
min-height: 1.5rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.wrn-next__carousel-pagination button {
|
|
width: 0.65rem;
|
|
min-height: 0.65rem;
|
|
padding: 0;
|
|
border: 1px solid var(--wrn-color-text-muted);
|
|
border-radius: 999px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.wrn-next__carousel-pagination button[data-active="true"] {
|
|
border-color: var(--wrn-carousel-color);
|
|
background: var(--wrn-carousel-color);
|
|
}
|
|
|
|
.wrn-next__carousel-counter {
|
|
position: absolute;
|
|
z-index: 3;
|
|
bottom: 0.8rem;
|
|
left: 50%;
|
|
padding: 0.25rem 0.65rem;
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--wrn-color-text) 82%, transparent);
|
|
color: var(--wrn-color-surface);
|
|
font-weight: 700;
|
|
transform: translateX(-50%);
|
|
}
|
|
|
|
.wrn-next__carousel-thumbnails {
|
|
display: flex;
|
|
overflow: auto;
|
|
gap: 0.55rem;
|
|
scrollbar-width: thin;
|
|
}
|
|
|
|
.wrn-next__carousel-thumbnails button {
|
|
display: grid;
|
|
flex: 0 0 8.5rem;
|
|
min-height: 3.75rem;
|
|
overflow: hidden;
|
|
padding: 0;
|
|
border: 1px solid var(--wrn-color-border);
|
|
border-radius: var(--wrn-radius-sm);
|
|
background: var(--wrn-color-surface);
|
|
color: var(--wrn-color-text);
|
|
cursor: pointer;
|
|
place-items: center;
|
|
}
|
|
|
|
.wrn-next__carousel-thumbnails button[data-active="true"] {
|
|
border-color: var(--wrn-carousel-color);
|
|
box-shadow: inset 0 0 0 1px var(--wrn-carousel-color);
|
|
}
|
|
|
|
.wrn-next__carousel-thumbnails img {
|
|
width: 100%;
|
|
height: 4rem;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.wrn-next__carousel-thumbnails span {
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails {
|
|
flex: 0 0 9rem;
|
|
flex-direction: column;
|
|
max-height: 28rem;
|
|
}
|
|
|
|
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails button {
|
|
flex-basis: 7.5rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.wrn-next--carousel[data-draggable="true"] .wrn-next__carousel-viewport {
|
|
cursor: grab;
|
|
touch-action: pan-y;
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
}
|
|
|
|
.wrn-next--carousel[data-draggable="true"] .wrn-next__carousel-viewport:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.wrn-next--carousel[data-draggable="true"] img,
|
|
.wrn-next--carousel[data-draggable="true"] a {
|
|
-webkit-user-drag: none;
|
|
}
|
|
|
|
.wrn-next--carousel[data-snap="true"] .wrn-next__carousel-viewport {
|
|
overflow-x: auto;
|
|
scroll-behavior: smooth;
|
|
scroll-snap-type: x mandatory;
|
|
scrollbar-width: thin;
|
|
}
|
|
|
|
.wrn-next--carousel[data-snap="true"] .wrn-next__carousel-track {
|
|
--wrn-carousel-snap-slide: calc(
|
|
(100% - (var(--wrn-carousel-per-view) - 1) * var(--wrn-carousel-gap)) /
|
|
var(--wrn-carousel-per-view)
|
|
);
|
|
display: grid;
|
|
width: 100%;
|
|
min-width: 100%;
|
|
box-sizing: content-box;
|
|
grid-auto-columns: var(--wrn-carousel-snap-slide);
|
|
grid-auto-flow: column;
|
|
padding-inline: calc((100% - var(--wrn-carousel-snap-slide)) / 2);
|
|
transform: none;
|
|
}
|
|
|
|
.wrn-next--carousel[data-snap="true"] .wrn-next__carousel-slide {
|
|
width: auto;
|
|
min-width: 0;
|
|
scroll-snap-align: center;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.wrn-next__carousel-slide {
|
|
flex-basis: 100%;
|
|
min-height: 16rem;
|
|
}
|
|
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-layout {
|
|
flex-direction: column-reverse;
|
|
}
|
|
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails {
|
|
flex: auto;
|
|
flex-direction: row;
|
|
max-height: none;
|
|
}
|
|
.wrn-next--carousel[data-thumbnails="vertical"] .wrn-next__carousel-thumbnails button {
|
|
flex-basis: 8.5rem;
|
|
width: auto;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.wrn-next__carousel-track {
|
|
transition-duration: 0.01ms;
|
|
}
|
|
}
|
|
|
|
/* Shared component foundation. */
|
|
|
|
.wrn-component {
|
|
--wrn-component-color: var(--wrn-color-primary);
|
|
--wrn-component-scale: 1;
|
|
margin: 0;
|
|
color: var(--wrn-color-text);
|
|
font-family: var(--wrn-font-sans, inherit);
|
|
}
|
|
|
|
.wrn-component--color-primary {
|
|
--wrn-component-color: var(--wrn-color-primary);
|
|
}
|
|
|
|
.wrn-component--color-secondary {
|
|
--wrn-component-color: var(--wrn-color-secondary);
|
|
}
|
|
|
|
.wrn-component--color-success {
|
|
--wrn-component-color: var(--wrn-color-success);
|
|
}
|
|
|
|
.wrn-component--color-warning {
|
|
--wrn-component-color: var(--wrn-color-warning);
|
|
}
|
|
|
|
.wrn-component--color-danger {
|
|
--wrn-component-color: var(--wrn-color-danger);
|
|
}
|
|
|
|
.wrn-component--color-info {
|
|
--wrn-component-color: var(--wrn-color-info);
|
|
}
|
|
|
|
.wrn-component--size-xs {
|
|
--wrn-component-scale: 0.78;
|
|
}
|
|
|
|
.wrn-component--size-sm {
|
|
--wrn-component-scale: 0.88;
|
|
}
|
|
|
|
.wrn-component--size-default,
|
|
.wrn-component--size-md {
|
|
--wrn-component-scale: 1;
|
|
}
|
|
|
|
.wrn-component--size-lg {
|
|
--wrn-component-scale: 1.14;
|
|
}
|
|
|
|
.wrn-component--size-xl {
|
|
--wrn-component-scale: 1.28;
|
|
}
|
|
|
|
.wrn-component:not(.wrn-btn) {
|
|
font-size: calc(1em * var(--wrn-component-scale));
|
|
}
|
|
|
|
.wrn-component :is(a, button, input, select, textarea):focus-visible {
|
|
outline-color: var(--wrn-component-color);
|
|
}
|
|
|
|
.wrn-component p {
|
|
margin: 0;
|
|
color: var(--wrn-color-muted);
|
|
line-height: 1.6;
|
|
}
|
|
}
|
|
}
|