Rotating rated quotes beside a photo of each person with a flush glass strip.

Last updated

Open block
Show code
---
/**
 * Split image 02: Rotating rated quotes beside a photo of each person with a flush glass strip.
 *
 * @block testimonials
 * @order 24
 */
import Section from '@/components/apogee/Section/Section.astro'
import Rating from '@/components/apogee/Rating/Rating.astro'
import Carousel from '@/components/apogee/Carousel/Carousel.astro'
import Image from '@/components/apogee/Image/Image.astro'
import type { TestimonialsContent } from '@/components/apogee/sections/_shared/testimonials.ts'

/** @composable */
interface Props extends TestimonialsContent {}

const {
  label = 'Testimonials',
  items = [
    { quote: 'We shipped our redesign in three weeks instead of three months. The team still talks about it.', name: 'Amara Nwosu', role: 'Head of Operations', company: 'Lumen', avatar: 'https://picsum.photos/id/1027/960/1440', rating: 5 },
    { quote: 'Every page we build now looks like it belongs together. Our designers finally trust what ships.', name: 'Daniel Reyes', role: 'Engineering Lead', company: 'Quanta', avatar: 'https://picsum.photos/id/91/960/1440', rating: 5 },
    { quote: 'Support answered in minutes, and the fix was live the same afternoon.', name: 'Sofia Marchetti', role: 'Product Designer', company: 'Fieldwork', avatar: 'https://picsum.photos/id/832/960/1440', rating: 5 },
  ],
} = Astro.props
---

<Section spacing="large">
  <Carousel label={label} visibleItems={{ base: 1 }} controlsPosition="below">
      {items.map((t) => (
      <div class="grid items-center gap-12 lg:grid-cols-2 lg:gap-16">
        <figure class="flex flex-col gap-8">
          {t.rating != null && <Rating value={t.rating} readonly size="small" color="warning" aria-label={`Rated ${t.rating} out of 5`} />}
          <blockquote class="text-2xl font-medium tracking-tight text-balance md:text-4xl line-clamp-5"><p>“{t.quote}”</p></blockquote>
          <figcaption class="flex flex-col"><span class="text-lg font-semibold">{t.name}</span>{t.company && <span class="text-neutral-faded">{t.company}</span>}</figcaption>
        </figure>
        <div class="relative isolate flex h-140 flex-col justify-end overflow-hidden lg:h-180">
          {t.avatar && <div class="absolute inset-0 -z-10"><Image src={t.avatar} alt="" width={960} height={1440} fit="cover" class="size-full" /></div>}
          <div class="flex flex-col gap-1 border-t border-neutral bg-page/80 p-5 backdrop-blur-md" aria-hidden="true"><span class="flex items-center justify-between gap-4 text-xl font-semibold">{t.name}{t.rating != null && <Rating value={t.rating} readonly size="small" color="warning" aria-label={`Rated ${t.rating} out of 5`} />}</span>{t.role && <span class="text-sm font-semibold">{t.role}</span>}{t.company && <span class="text-sm text-neutral-faded">{t.company}</span>}</div>
        </div>
      </div>
      ))}
    </Carousel>
</Section>

Add

npx @ava-ui/apogee add testimonials/split-image-02

This copies the block to src/components/apogee/blocks/testimonials/ with everything it uses: Carousel, Image, Rating, Section. Every piece of copy is a prop, with the sample copy as its default. Pass yours, or null to hide a part; see copy is props.

Accessibility

  • The stars are read as one rating (“Rated 5 out of 5”).
  • The carousel is a named region; its arrows are buttons and it scrolls by swipe or keyboard too.
  • Photos that only decorate have an empty alt; give yours one when it carries meaning.