Skip to content

Commit

Permalink
remove slider island
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Apr 22, 2024
1 parent 0cb48a3 commit 251f1ee
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 252 deletions.
3 changes: 1 addition & 2 deletions components/header/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Slider from "../../components/ui/Slider.tsx";
import SliderJS from "../../islands/SliderJS.tsx";
import { useId } from "../../sdk/useId.ts";

export interface Props {
Expand All @@ -26,7 +25,7 @@ function Alert({ alerts = [], interval = 5 }: Props) {
))}
</Slider>

<SliderJS rootId={id} interval={interval && interval * 1e3} />
<Slider.JS rootId={id} interval={interval && interval * 1e3} />
</div>
);
}
Expand Down
11 changes: 5 additions & 6 deletions components/layout/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import Icon from "../../components/ui/Icon.tsx";
import Slider from "../../components/ui/Slider.tsx";
import SliderJS from "../../islands/SliderJS.tsx";
import { clx } from "../../sdk/clx.ts";
import type { Section } from "deco/blocks/section.ts";
import { ComponentChildren, toChildArray } from "preact";
import { useId } from "preact/hooks";
import { buttonClasses, ButtonColor, grid } from "../../constants.tsx";
import Icon from "../../components/ui/Icon.tsx";
import Slider from "../../components/ui/Slider.tsx";
import { ButtonColor, buttonClasses, grid } from "../../constants.tsx";
import { clx } from "../../sdk/clx.ts";

interface Layout {
/** @description For desktop in px. */
Expand Down Expand Up @@ -123,7 +122,7 @@ function Section({ interval = 0, layout, style, children }: Props) {
</ul>
)}

<SliderJS rootId={id} interval={interval && interval * 1e3} infinite />
<Slider.JS rootId={id} interval={interval && interval * 1e3} infinite />
</div>
</>
);
Expand Down
3 changes: 1 addition & 2 deletions components/product/Gallery/ImageSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Image from "apps/website/components/Image.tsx";
import Icon from "../../../components/ui/Icon.tsx";
import Slider from "../../../components/ui/Slider.tsx";
import ProductImageZoom from "../../../islands/ProductImageZoom.tsx";
import SliderJS from "../../../islands/SliderJS.tsx";
import { useId } from "../../../sdk/useId.ts";

export interface Props {
Expand Down Expand Up @@ -105,7 +104,7 @@ export default function GallerySlider(props: Props) {
))}
</ul>

<SliderJS rootId={id} />
<Slider.JS rootId={id} />
</div>
);
}
9 changes: 4 additions & 5 deletions components/product/ProductImageZoom.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useSignal } from "@preact/signals";
import type { ImageObject } from "apps/commerce/types.ts";
import Image from "apps/website/components/Image.tsx";
import Button from "../../components/ui/Button.tsx";
import Icon from "../../components/ui/Icon.tsx";
import Modal from "../../components/ui/Modal.tsx";
import Slider from "../../components/ui/Slider.tsx";
import SliderJS from "../../islands/SliderJS.tsx";
import { useId } from "../../sdk/useId.ts";
import { useSignal } from "@preact/signals";
import type { ImageObject } from "apps/commerce/types.ts";
import Image from "apps/website/components/Image.tsx";

export interface Props {
images: ImageObject[];
Expand Down Expand Up @@ -59,7 +58,7 @@ function ProductImageZoom({ images, width, height }: Props) {
<Icon size={24} id="ChevronRight" strokeWidth={3} />
</Slider.NextButton>

<SliderJS rootId={id} />
<Slider.JS rootId={id} />
</div>
</Modal>
</div>
Expand Down
9 changes: 4 additions & 5 deletions components/product/ProductShelf.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type { Product } from "apps/commerce/types.ts";
import { mapProductToAnalyticsItem } from "apps/commerce/utils/productToAnalyticsItem.ts";
import { SendEventOnView } from "../../components/Analytics.tsx";
import ProductCard from "../../components/product/ProductCard.tsx";
import Icon from "../../components/ui/Icon.tsx";
import Slider from "../../components/ui/Slider.tsx";
import SliderJS from "../../islands/SliderJS.tsx";
import { clx } from "../../sdk/clx.ts";
import { useId } from "../../sdk/useId.ts";
import { useOffer } from "../../sdk/useOffer.ts";
import { usePlatform } from "../../sdk/usePlatform.tsx";
import type { Product } from "apps/commerce/types.ts";
import { mapProductToAnalyticsItem } from "apps/commerce/utils/productToAnalyticsItem.ts";
import { clx } from "../../sdk/clx.ts";

export interface Props {
products: Product[] | null;
Expand Down Expand Up @@ -101,7 +100,7 @@ function ProductShelf({
</div>
</>
)}
<SliderJS rootId={id} />
<Slider.JS rootId={id} />
<SendEventOnView
id={id}
event={{
Expand Down
3 changes: 1 addition & 2 deletions components/product/ProductShelfTabbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { SendEventOnView } from "../../components/Analytics.tsx";
import ProductCard from "../../components/product/ProductCard.tsx";
import Icon from "../../components/ui/Icon.tsx";
import Slider from "../../components/ui/Slider.tsx";
import SliderJS from "../../islands/SliderJS.tsx";
import { useId } from "../../sdk/useId.ts";
import { useOffer } from "../../sdk/useOffer.ts";
import { usePlatform } from "../../sdk/usePlatform.tsx";
Expand Down Expand Up @@ -94,7 +93,7 @@ function TabbedProductShelf({
</Slider.NextButton>
</div>
</>
<SliderJS rootId={id} />
<Slider.JS rootId={id} />
<SendEventOnView
id={id}
event={{
Expand Down
8 changes: 3 additions & 5 deletions components/ui/BannerCarousel.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type { ImageWidget } from "apps/admin/widgets.ts";
import { Picture, Source } from "apps/website/components/Picture.tsx";
import {
SendEventOnClick,
SendEventOnView,
} from "../../components/Analytics.tsx";
import Button from "../../components/ui/Button.tsx";
import Icon from "../../components/ui/Icon.tsx";
import Slider from "../../components/ui/Slider.tsx";
import SliderJS from "../../islands/SliderJS.tsx";
import { useId } from "../../sdk/useId.ts";
import type { ImageWidget } from "apps/admin/widgets.ts";
import { Picture, Source } from "apps/website/components/Picture.tsx";
import Image from "apps/website/components/Image.tsx";

/**
* @titleBy alt
Expand Down Expand Up @@ -255,7 +253,7 @@ function BannerCarousel(props: Props) {

{props.dots && <Dots images={images} interval={interval} />}

<SliderJS rootId={id} interval={interval && interval * 1e3} infinite />
<Slider.JS rootId={id} interval={interval && interval * 1e3} infinite />
</div>
);
}
Expand Down
199 changes: 199 additions & 0 deletions components/ui/Slider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ComponentChildren, JSX } from "preact";
import { scriptAsDataURI } from "apps/utils/dataURI.ts";

function Dot({ index, children }: {
index: number;
Expand Down Expand Up @@ -34,9 +35,207 @@ function PrevButton(props: JSX.IntrinsicElements["button"]) {
return <button data-slide="prev" aria-label="Previous item" {...props} />;
}

export interface Props {
rootId: string;
scroll?: "smooth" | "auto";
interval?: number;
infinite?: boolean;
}

const setup = ({ rootId, scroll, interval, infinite }: Props) => {
const ATTRIBUTES = {
"data-slider": "data-slider",
"data-slider-item": "data-slider-item",
'data-slide="prev"': 'data-slide="prev"',
'data-slide="next"': 'data-slide="next"',
"data-dot": "data-dot",
};

// Percentage of the item that has to be inside the container
// for it it be considered as inside the container
const THRESHOLD = 0.6;

const intersectionX = (element: DOMRect, container: DOMRect): number => {
const delta = container.width / 1_000;

if (element.right < container.left - delta) {
return 0.0;
}

if (element.left > container.right + delta) {
return 0.0;
}

if (element.left < container.left - delta) {
return element.right - container.left + delta;
}

if (element.right > container.right + delta) {
return container.right - element.left + delta;
}

return element.width;
};

// as any are ok in typeguard functions
const isHTMLElement = (x: Element): x is HTMLElement =>
// deno-lint-ignore no-explicit-any
typeof (x as any).offsetLeft === "number";

const root = document.getElementById(rootId);
const slider = root?.querySelector(`[${ATTRIBUTES["data-slider"]}]`);
const items = root?.querySelectorAll(`[${ATTRIBUTES["data-slider-item"]}]`);
const prev = root?.querySelector(`[${ATTRIBUTES['data-slide="prev"']}]`);
const next = root?.querySelector(`[${ATTRIBUTES['data-slide="next"']}]`);
const dots = root?.querySelectorAll(`[${ATTRIBUTES["data-dot"]}]`);

if (!root || !slider || !items || items.length === 0) {
console.warn(
"Missing necessary slider attributes. It will not work as intended. Necessary elements:",
{ root, slider, items, rootId },
);

return;
}

const getElementsInsideContainer = () => {
const indices: number[] = [];
const sliderRect = slider.getBoundingClientRect();

for (let index = 0; index < items.length; index++) {
const item = items.item(index);
const rect = item.getBoundingClientRect();

const ratio = intersectionX(
rect,
sliderRect,
) / rect.width;

if (ratio > THRESHOLD) {
indices.push(index);
}
}

return indices;
};

const goToItem = (index: number) => {
const item = items.item(index);

if (!isHTMLElement(item)) {
console.warn(
`Element at index ${index} is not an html element. Skipping carousel`,
);

return;
}

slider.scrollTo({
top: 0,
behavior: scroll,
left: item.offsetLeft - root.offsetLeft,
});
};

const onClickPrev = () => {
const indices = getElementsInsideContainer();
// Wow! items per page is how many elements are being displayed inside the container!!
const itemsPerPage = indices.length;

const isShowingFirst = indices[0] === 0;
const pageIndex = Math.floor(indices[indices.length - 1] / itemsPerPage);

goToItem(
isShowingFirst ? items.length - 1 : (pageIndex - 1) * itemsPerPage,
);
};

const onClickNext = () => {
const indices = getElementsInsideContainer();
// Wow! items per page is how many elements are being displayed inside the container!!
const itemsPerPage = indices.length;

const isShowingLast = indices[indices.length - 1] === items.length - 1;
const pageIndex = Math.floor(indices[0] / itemsPerPage);

goToItem(isShowingLast ? 0 : (pageIndex + 1) * itemsPerPage);
};

const observer = new IntersectionObserver(
(elements) =>
elements.forEach((item) => {
const index = Number(item.target.getAttribute("data-slider-item")) || 0;
const dot = dots?.item(index);

if (item.isIntersecting) {
dot?.setAttribute("disabled", "");
} else {
dot?.removeAttribute("disabled");
}

if (!infinite) {
if (index === 0) {
if (item.isIntersecting) {
prev?.setAttribute("disabled", "");
} else {
prev?.removeAttribute("disabled");
}
}
if (index === items.length - 1) {
if (item.isIntersecting) {
next?.setAttribute("disabled", "");
} else {
next?.removeAttribute("disabled");
}
}
}
}),
{ threshold: THRESHOLD, root: slider },
);

items.forEach((item) => observer.observe(item));

for (let it = 0; it < (dots?.length ?? 0); it++) {
dots?.item(it).addEventListener("click", () => goToItem(it));
}

prev?.addEventListener("click", onClickPrev);
next?.addEventListener("click", onClickNext);

const timeout = interval && setInterval(onClickNext, interval);

// Unregister callbacks
return () => {
for (let it = 0; it < (dots?.length ?? 0); it++) {
dots?.item(it).removeEventListener("click", () => goToItem(it));
}

prev?.removeEventListener("click", onClickPrev);
next?.removeEventListener("click", onClickNext);

observer.disconnect();

clearInterval(timeout);
};
};

function JS({
rootId,
scroll = "smooth",
interval,
infinite = false,
}: Props) {
return (
<script
src={scriptAsDataURI(setup, { rootId, scroll, interval, infinite })}
/>
);
}

Slider.Dot = Dot;
Slider.Item = Item;
Slider.NextButton = NextButton;
Slider.PrevButton = PrevButton;
Slider.JS = JS;

export default Slider;
Loading

0 comments on commit 251f1ee

Please sign in to comment.