HeroCarousel.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. "use client";
  2. import * as React from "react";
  3. import Image from "next/image";
  4. import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
  5. import { GridTileImage } from "@/components/theme/ui/grid/Tile";
  6. import { Shimmer } from "@/components/common/Shimmer";
  7. import {
  8. HeroCarouselShimmer,
  9. HeroCarouselThumbnailShimmer,
  10. } from "./HeroCarouselShimmer";
  11. export default function HeroCarousel({
  12. images,
  13. }: {
  14. images: { src: string; altText: string }[];
  15. }) {
  16. const [current, setCurrent] = React.useState(0);
  17. const [isLoading, setIsLoading] = React.useState(true);
  18. const prevSlide = () => {
  19. setIsLoading(true);
  20. setCurrent((prev) => (prev === 0 ? images.length - 1 : prev - 1));
  21. };
  22. const nextSlide = () => {
  23. setIsLoading(true);
  24. setCurrent((prev) => (prev === images.length - 1 ? 0 : prev + 1));
  25. };
  26. if (!images || images.length === 0) {
  27. return (
  28. <>
  29. <HeroCarouselShimmer />
  30. <HeroCarouselThumbnailShimmer count={3} />
  31. </>
  32. );
  33. }
  34. const buttonClassName =
  35. "flex h-full cursor-pointer items-center justify-center px-6 transition-all ease-in-out hover:scale-110 hover:text-black dark:hover:text-white";
  36. return (
  37. <>
  38. <div className="group relative overflow-hidden">
  39. <div
  40. key={current}
  41. className="group relative h-full max-h-[738px] w-full overflow-hidden rounded-2xl transition-opacity duration-600 ease-in-out"
  42. style={{
  43. aspectRatio: "380/316",
  44. }}
  45. >
  46. <div className="relative h-full w-full">
  47. {isLoading && (
  48. <Shimmer
  49. className="absolute inset-0 z-10 h-full w-full"
  50. rounded="lg"
  51. />
  52. )}
  53. <Image
  54. fill
  55. alt={images[current]?.altText as string}
  56. className={`h-full w-full object-cover transition duration-300 ease-in-out group-hover:scale-105 ${
  57. isLoading ? "opacity-0" : "opacity-100"
  58. }`}
  59. priority={true}
  60. sizes="(max-width: 1024px) 100vw, (max-width: 1536px) 66vw, 1000px"
  61. src={images[current]?.src as string}
  62. onLoad={() => setIsLoading(false)}
  63. onError={() => setIsLoading(false)}
  64. />
  65. </div>
  66. </div>
  67. {images?.length > 1 ? (
  68. <div className="absolute bottom-[5%] flex w-full justify-center">
  69. <div className="mx-auto flex h-11 items-center rounded-full border border-gray-500/70 bg-gray-500/70 text-white/80 backdrop-blur transition-all duration-300 dark:border-black dark:bg-neutral-900/80">
  70. <button
  71. aria-label="Previous image"
  72. className={buttonClassName}
  73. onClick={prevSlide}
  74. >
  75. <ArrowLeftIcon className="h-5" />
  76. </button>
  77. <div className="mx-1 h-6 w-px bg-white/80" />
  78. <button
  79. aria-label="Next image"
  80. className={buttonClassName}
  81. onClick={nextSlide}
  82. >
  83. <ArrowRightIcon className="h-5" />
  84. </button>
  85. </div>
  86. </div>
  87. ) : null}
  88. </div>
  89. {images?.length > 1 ? (
  90. <ul className="fade-in my-3 flex flex-nowrap gap-2 overflow-x-auto overflow-y-hidden py-1 sm:my-7 lg:mb-0">
  91. {images.map((image, index) => {
  92. const isActive = index === current;
  93. return (
  94. <li
  95. key={image.src}
  96. className="relative aspect-square w-16 md:w-32 flex-shrink-0"
  97. >
  98. <button
  99. className="h-full w-full"
  100. onClick={() => {
  101. setCurrent(index);
  102. }}
  103. >
  104. <GridTileImage
  105. active={isActive}
  106. alt={image.altText}
  107. fill
  108. objectFit="cover"
  109. src={image.src}
  110. sizes="(max-width: 768px) 16vw, 10vw"
  111. />
  112. </button>
  113. </li>
  114. );
  115. })}
  116. </ul>
  117. ) : isLoading ? (
  118. <div className="my-3 sm:my-7 lg:mb-0">
  119. <div className="relative aspect-square w-16 md:w-32">
  120. <Shimmer className="h-full w-full" rounded="lg" />
  121. </div>
  122. </div>
  123. ) : null}
  124. </>
  125. );
  126. }