1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-05 05:05:17 +02:00

feat: update CardCarousel component to handle images, name, and icon props across various cards

This commit is contained in:
Sean Morley 2025-07-14 19:11:38 -04:00
parent 7a61ba2d22
commit d53991e3f9
8 changed files with 76 additions and 67 deletions

View file

@ -1,43 +1,33 @@
<script lang="ts">
import type { Location, Lodging, Transportation } from '$lib/types';
import ImageDisplayModal from './ImageDisplayModal.svelte';
import { t } from 'svelte-i18n';
export let adventures: Location[] | Transportation[] | Lodging[] = [];
import type { ContentImage } from '$lib/types';
export let images: ContentImage[] = [];
export let name: string = '';
export let icon: string = '';
let currentSlide = 0;
let showImageModal = false;
let modalInitialIndex = 0;
$: adventure_images = adventures.flatMap((adventure) =>
adventure.images.map((image) => ({
image: image.image,
adventure: adventure,
is_primary: image.is_primary
}))
);
$: sortedImages = [...images].sort((a, b) => {
if (a.is_primary && !b.is_primary) {
return -1;
} else if (!a.is_primary && b.is_primary) {
return 1;
} else {
return 0;
}
});
$: {
if (adventure_images.length > 0) {
if (sortedImages.length > 0) {
currentSlide = 0;
}
}
$: {
// sort so that any image in adventure_images .is_primary is first
adventure_images.sort((a, b) => {
if (a.is_primary && !b.is_primary) {
return -1;
} else if (!a.is_primary && b.is_primary) {
return 1;
} else {
return 0;
}
});
}
function changeSlide(direction: string) {
if (direction === 'next' && currentSlide < adventure_images.length - 1) {
if (direction === 'next' && currentSlide < sortedImages.length - 1) {
currentSlide = currentSlide + 1;
} else if (direction === 'prev' && currentSlide > 0) {
currentSlide = currentSlide - 1;
@ -54,16 +44,17 @@
}
</script>
{#if showImageModal && adventure_images.length > 0}
{#if showImageModal && sortedImages.length > 0}
<ImageDisplayModal
images={adventure_images}
images={sortedImages}
initialIndex={modalInitialIndex}
on:close={closeImageModal}
{name}
/>
{/if}
<figure>
{#if adventure_images && adventure_images.length > 0}
{#if sortedImages && sortedImages.length > 0}
<div class="carousel w-full relative">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="carousel-item w-full block">
@ -75,17 +66,17 @@
class="cursor-pointer relative group"
>
<img
src={adventure_images[currentSlide].image}
src={sortedImages[currentSlide].image}
class="w-full h-48 object-cover transition-all group-hover:brightness-110"
alt={adventure_images[currentSlide].adventure.name}
alt={name || 'Image'}
/>
<!-- Overlay indicator for multiple images -->
<!-- {#if adventure_images.length > 1}
<!-- {#if sortedImages.length > 1}
<div
class="absolute top-3 right-3 bg-black/60 text-white px-2 py-1 rounded-lg text-xs font-medium"
>
{currentSlide + 1} / {adventure_images.length}
{currentSlide + 1} / {sortedImages.length}
</div>
{/if} -->
@ -113,7 +104,7 @@
</div> -->
</a>
{#if adventure_images.length > 1}
{#if sortedImages.length > 1}
<div class="absolute inset-0 flex items-center justify-between pointer-events-none">
{#if currentSlide > 0}
<button
@ -134,7 +125,7 @@
<div class="w-12"></div>
{/if}
{#if currentSlide < adventure_images.length - 1}
{#if currentSlide < sortedImages.length - 1}
<button
on:click|stopPropagation={() => changeSlide('next')}
class="btn btn-circle btn-sm mr-2 pointer-events-auto bg-neutral border-none text-neutral-content shadow-lg"
@ -155,9 +146,9 @@
</div>
<!-- Dot indicators at bottom -->
<!-- {#if adventure_images.length > 1}
<!-- {#if sortedImages.length > 1}
<div class="absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-2">
{#each adventure_images as _, index}
{#each sortedImages as _, index}
<button
on:click|stopPropagation={() => (currentSlide = index)}
class="w-2 h-2 rounded-full transition-all pointer-events-auto {index ===
@ -173,14 +164,21 @@
</div>
</div>
{:else}
<!-- add a figure with a gradient instead -->
<div class="w-full h-48 bg-gradient-to-r from-success via-base to-primary relative">
<!-- subtle button bottom left text
<div
class="absolute bottom-0 left-0 px-2 py-1 text-md font-medium bg-neutral rounded-tr-lg shadow-md"
>
{$t('adventures.no_image_found')}
</div> -->
<!-- Fallback with emoji icon as main image -->
<div class="w-full h-48 relative flex items-center justify-center">
{#if icon}
<!-- Clean background with emoji as the focal point -->
<div
class="w-full h-full bg-gradient-to-r from-success via-base to-primary flex items-center justify-center"
>
<div class="text-8xl select-none">
{icon}
</div>
</div>
{:else}
<!-- Original gradient fallback when no icon -->
<div class="w-full h-full bg-gradient-to-r from-success via-base to-primary"></div>
{/if}
</div>
{/if}
</figure>

View file

@ -91,7 +91,11 @@
>
<!-- Image Carousel -->
<div class="relative overflow-hidden rounded-t-2xl">
<CardCarousel adventures={collection.locations} />
<CardCarousel
images={collection.locations.flatMap((location) => location.images)}
name={collection.name}
icon="📚"
/>
<!-- Badge Overlay -->
<div class="absolute top-4 left-4 flex flex-col gap-2">

View file

@ -3,15 +3,15 @@
const dispatch = createEventDispatcher();
import { onMount } from 'svelte';
let modal: HTMLDialogElement;
import type { Location } from '$lib/types';
export let images: { image: string; adventure: any | null }[] = [];
export let initialIndex: number = 0;
import { t } from 'svelte-i18n';
import type { ContentImage } from '$lib/types';
export let images: ContentImage[] = [];
export let initialIndex: number = 0;
export let name: string = '';
export let location: string = '';
let currentIndex = initialIndex;
let currentImage = images[currentIndex]?.image || '';
let currentAdventure = images[currentIndex]?.adventure || null;
onMount(() => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
@ -48,7 +48,6 @@
function updateCurrentSlide(index: number) {
currentIndex = index;
currentImage = images[currentIndex]?.image || '';
currentAdventure = images[currentIndex]?.adventure || null;
}
function nextSlide() {
@ -86,7 +85,7 @@
on:keydown={handleKeydown}
tabindex="0"
>
{#if currentAdventure && currentImage}
{#if images.length > 0 && currentImage}
<!-- Header -->
<div
class="top-0 z-10 bg-base-100/90 backdrop-blur-lg border-b border-base-300 -mx-6 -mt-6 px-6 py-4 mb-6"
@ -110,7 +109,7 @@
</div>
<div>
<h1 class="text-2xl font-bold text-primary">
{currentAdventure.name}
{name}
</h1>
{#if images.length > 1}
<p class="text-sm text-base-content/60">
@ -174,7 +173,7 @@
<div class="flex justify-center items-center max-w-full">
<img
src={currentImage}
alt={currentAdventure.name}
alt={name}
class="max-w-full max-h-[75vh] object-contain rounded-lg shadow-lg"
style="max-width: 100%; max-height: 75vh; object-fit: contain;"
/>
@ -210,11 +209,7 @@
: 'border-base-300 hover:border-base-400'}"
on:click={() => goToSlide(index)}
>
<img
src={imageData.image}
alt={imageData.adventure?.name || 'Image'}
class="w-full h-full object-cover"
/>
<img src={imageData.image} alt={name} class="w-full h-full object-cover" />
</button>
{/each}
</div>
@ -227,7 +222,7 @@
>
<div class="flex items-center justify-between">
<div class="text-sm text-base-content/60">
{#if currentAdventure.location}
{#if location}
<span class="flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
@ -243,7 +238,7 @@
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
{currentAdventure.location}
{location}
</span>
{/if}
</div>

View file

@ -190,7 +190,7 @@
>
<!-- Image Section with Overlay -->
<div class="relative overflow-hidden rounded-t-2xl">
<CardCarousel adventures={[adventure]} />
<CardCarousel images={adventure.images} icon={adventure.category?.icon} name={adventure.name} />
<!-- Status Overlay -->
<div class="absolute top-4 left-4 flex flex-col gap-2">

View file

@ -99,7 +99,7 @@
>
<!-- Image Section with Overlay -->
<div class="relative overflow-hidden rounded-t-2xl">
<CardCarousel adventures={[lodging]} />
<CardCarousel images={lodging.images} icon={getLodgingIcon(lodging.type)} name={lodging.name} />
<!-- Category Badge -->
{#if lodging.type}

View file

@ -118,7 +118,11 @@
>
<!-- Image Section with Overlay -->
<div class="relative overflow-hidden rounded-t-2xl">
<CardCarousel adventures={[transportation]} />
<CardCarousel
images={transportation.images}
icon={getTransportationIcon(transportation.type)}
name={transportation.name}
/>
<!-- Privacy Indicator -->
<div class="absolute top-4 right-4">

View file

@ -1224,7 +1224,7 @@
{/if}
</div>
</div>
{#if orderedItem.type === 'adventure' && orderedItem.item && 'images' in orderedItem.item}
{#if orderedItem.type === 'adventure' && orderedItem.item && 'visits' in orderedItem.item}
<LocationCard
user={data.user}
on:edit={editAdventure}
@ -1293,7 +1293,11 @@
{#if isPopupOpen}
<Popup openOn="click" offset={[0, -10]} on:close={() => (isPopupOpen = false)}>
{#if adventure.images && adventure.images.length > 0}
<CardCarousel adventures={[adventure]} />
<CardCarousel
images={adventure.images}
name={adventure.name}
icon={adventure?.category?.icon}
/>
{/if}
<div class="text-lg text-black font-bold">{adventure.name}</div>
<p class="font-semibold text-black text-md">

View file

@ -208,7 +208,11 @@
<div class="min-w-64 max-w-sm">
{#if adventure.images && adventure.images.length > 0}
<div class="mb-3">
<CardCarousel adventures={[adventure]} />
<CardCarousel
images={adventure.images}
name={adventure.name}
icon={adventure?.category?.icon}
/>
</div>
{/if}
<div class="space-y-2">