@archpublicwebsite/gallery
v1.0.1
Published
Gallery lightbox component for Archipelago hotel websites
Maintainers
Readme
@archpublicwebsite/gallery
Full-screen image lightbox gallery component for Vue 3 — works in plain Vue, Nuxt, Vuetify, and any other Vue-based framework.
Features
- Fully typed — strict TypeScript interfaces for all props, emits, and composable return values
- FLIP animations — smooth open/close transitions matched to the source image position
- Swiper-powered — swipe, keyboard navigation, fade/slide effect, accessibility module
- Zoom — click or pinch to zoom, configurable max ratio
- Thumbnail strip — responsive breakpoints, active indicator
- Framework-agnostic styling — Tailwind compiled to plain CSS at build time; no Tailwind required by consumers
- SSR compatible — Swiper mounted only after
onMounted; safe for Nuxt - Accessible — ARIA labels, keyboard navigation (
←→),prefers-reduced-motionrespected - Zero peer deps besides Vue — Swiper is bundled; nothing else to install
Installation
npm install @archpublicwebsite/gallery
# or
pnpm add @archpublicwebsite/gallery
# or
yarn add @archpublicwebsite/galleryQuick Start
<script setup lang="ts">
import type { GalleryImageInput } from "@archpublicwebsite/gallery";
import { ModalGallery, useGallery } from "@archpublicwebsite/gallery";
import '@archpublicwebsite/gallery/style.css';
const images: GalleryImageInput[] = [
{ id: '1', url: 'https://...', alt: 'Pool', caption: 'Infinity Pool' },
{ id: '2', url: 'https://...', alt: 'Lobby', caption: 'Grand Lobby' },
]
const { isOpen, currentIndex, galleryContent, openGallery, closeGallery }
= useGallery(images)
</script>
<template>
<!-- trigger — can be a grid, a single button, anything -->
<img
v-for="(item, i) in galleryContent.items"
:key="item.id"
:src="item.thumbnail || item.url"
:alt="item.alt"
@click="openGallery(i)"
/>
<!-- lightbox -->
<ModalGallery
v-if="isOpen"
:content="galleryContent"
:current-index="currentIndex"
style="position: fixed; inset: 0; z-index: 9999"
@close="closeGallery"
/>
</template>Framework Examples
Nuxt 3
No plugin needed — import directly in the component.
<script setup lang="ts">
import { ModalGallery, useGallery } from '@archpublicwebsite/gallery';
import '@archpublicwebsite/gallery/style.css';
// ...same as quick start
</script>Vuetify (inside v-dialog)
<template>
<v-dialog v-model="isOpen" fullscreen>
<ModalGallery
:content="galleryContent"
:current-index="currentIndex"
@close="closeGallery"
/>
</v-dialog>
</template>React (via any Vue-in-React bridge)
The component is a standard Vue 3 SFC and follows the same composition pattern as @archpublicwebsite/modal and @archpublicwebsite/rangepicker.
useGallery(images) Composable
The composable is the recommended way to manage gallery state. It accepts a static or reactive array of GalleryImageInput objects and returns everything needed to wire up <ModalGallery>.
Input type — GalleryImageInput
| Field | Type | Required | Description |
| --------------- | --------- | -------- | -------------------------------------------- |
| id | string | ✅ | Unique identifier |
| url | string | ✅ | Full-resolution image URL |
| thumbnail | string | — | Thumbnail URL; derived from url if omitted |
| alt | string | — | Accessible alt text; falls back to title |
| caption | string | — | Short label shown in the overlay |
| title | string | — | Secondary label; used as alt fallback |
| description | string | — | Longer text shown below caption |
| [key: string] | unknown | — | Extra API fields are silently ignored |
// TypeScript enforces id + url at compile time
const images: GalleryImageInput[] = [
{ id: '1', url: 'https://...', alt: 'Pool' }, // ✅
{ url: 'https://...' }, // ✗ missing id
{ id: 1, url: "https://..." }, // ✗ id must be string
]Return value — UseGalleryReturn
| Property | Type | Description |
| --------------------- | ----------------------------- | ------------------------------------- |
| isOpen | Ref<boolean> | Whether the lightbox is visible |
| currentIndex | Ref<number> | Active slide index |
| galleryContent | ComputedRef<GalleryContent> | Structured data to pass to :content |
| openGallery(index?) | (index?: number) => void | Open at a given index (default: 0) |
| closeGallery() | () => void | Close the lightbox |
Reactive images example (fetched from an API):
const { data: rooms } = await useFetch('/api/rooms');
const { isOpen, galleryContent, openGallery, closeGallery } = useGallery(
() =>
rooms.value?.map(r => ({ id: r.id, url: r.image, alt: r.name })) ?? [],
)<ModalGallery> Props
| Prop | Type | Default | Description |
| ----------------- | ----------------- | --------- | ------------------------------------------------- |
| content | GalleryContent | required | Gallery data — use useGallery() to produce this |
| currentIndex | number | 0 | Active slide on mount |
| lightboxOptions | LightboxOptions | see below | Behaviour overrides |
LightboxOptions
| Option | Type | Default | Description |
| ---------------- | -------------------------------- | ---------- | ------------------------------------- |
| showThumbnails | boolean | true | Show thumbnail strip at the bottom |
| showNavigation | boolean | true | Show prev/next arrows |
| showPagination | boolean | true | Show fraction counter (e.g. "3 / 12") |
| showCaption | boolean | true | Show caption and description overlay |
| enableZoom | boolean | true | Enable click/pinch to zoom |
| thumbnailSize | 'small' \| 'medium' \| 'large' | 'medium' | Thumbnail strip size |
| effect | 'slide' \| 'fade' | 'slide' | Slide transition effect |
| maxZoom | number | 3 | Maximum zoom ratio |
All options have TypeScript autocomplete and hover documentation.
// ✗ TypeScript catches this at compile time:
<ModalGallery :lightbox-options="{ thumbnailSize: 'huge' }" />
// ^^^^^^ not assignable to ThumbnailSizeEvents
| Event | Payload | Description |
| -------------- | ------------------------------------ | ---------------------------------------------------------------------- |
| close | — | Fired when the user closes the gallery (close button or animation end) |
| image-change | (index: number, item: GalleryItem) | Fired when the active slide changes |
<ModalGallery
...
@close="closeGallery"
@image-change="
(index, item) => analytics.track('gallery_view', { id: item.id })
"
/>Animations
The gallery uses FLIP animations to match the position and size of the source image on open, creating a natural expand-from-thumbnail effect. On close, a ghost image animates back to the original position.
- Detects the source
<img>element in the DOM by matching the URL - Falls back to a simple fade if the source cannot be found
- Respects
prefers-reduced-motion: uses a short opacity fade instead
Auto-hiding Chrome
When content.items has only one image, navigation arrows, pagination, and the thumbnail strip are automatically hidden regardless of lightboxOptions. No extra config needed.
Styling & Theming
The package ships a single compiled CSS file — import it once at the top of your app or in the component:
import '@archpublicwebsite/gallery/style.css';The gallery uses scoped class names prefixed with arch-gallery- to avoid collisions with Vuetify, Bootstrap, or any other UI framework.
Publishing a new version
cd packages/gallery
pnpm version:patch # 1.0.0 → 1.0.1
pnpm version:minor # 1.0.0 → 1.1.0
pnpm version:major # 1.0.0 → 2.0.0
npm publishprepublishOnly runs pnpm build automatically before publishing.
