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

View file

@ -91,7 +91,11 @@
> >
<!-- Image Carousel --> <!-- Image Carousel -->
<div class="relative overflow-hidden rounded-t-2xl"> <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 --> <!-- Badge Overlay -->
<div class="absolute top-4 left-4 flex flex-col gap-2"> <div class="absolute top-4 left-4 flex flex-col gap-2">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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