Created
December 30, 2024 05:51
-
-
Save maietta/87f18e4290259c5c50ded0bd6442f2f3 to your computer and use it in GitHub Desktop.
Svelte 5 reactive grid/list layout.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script lang="ts"> | |
| import { preferences } from '$lib/stores/preferences'; // Reactive store | |
| import Pagination from './pagination.svelte'; | |
| import { truncateDescription, glance } from '$lib/realty/presentation'; | |
| let { data } = $props(); | |
| let selectedIndex = $state(1); | |
| let listings = $derived(data.results.records); | |
| let pagination = $derived(data.results.pagination); | |
| $effect(() => { | |
| const params = new URLSearchParams(window.location.search); | |
| selectedIndex = Number(params.get('page')) || 1; | |
| }); | |
| function getListedBy(record: any): { | |
| agents: { role: string; name: string; office: string }[]; | |
| } { | |
| const { | |
| LA1AgentFirstName, | |
| LA1AgentLastName, | |
| LO1OfficeName, | |
| LA2AgentFirstName, | |
| LA2AgentLastName, | |
| LO2OfficeName | |
| } = record.data; | |
| const agents = [ | |
| LA1AgentFirstName && | |
| LA1AgentLastName && | |
| LO1OfficeName && { | |
| role: 'Listed by', | |
| name: `${LA1AgentFirstName} ${LA1AgentLastName}`, | |
| office: LO1OfficeName | |
| }, | |
| LA2AgentFirstName && | |
| LO2OfficeName && { | |
| role: 'Co-Listed by', | |
| name: `${LA2AgentFirstName} ${LA2AgentLastName || ''}`.trim(), | |
| office: LO2OfficeName | |
| } | |
| ].filter(Boolean) as { role: string; name: string; office: string }[]; | |
| console.log(agents); | |
| return { agents }; | |
| } | |
| const layout = $derived($preferences.layout); | |
| </script> | |
| <!-- | |
| <svelte:head> | |
| <title>{children?.()?.props?.title}</title> | |
| </svelte:head> --> | |
| <!-- Listing Info Layout --> | |
| {#snippet listingInfo(listing: { data: { Class: any; Type: any } })} | |
| <ol class="flex grow flex-col"> | |
| <li class="text-right text-sm italic"> | |
| {listing.data.Class} ({listing.data.Type}) | |
| </li> | |
| {#each glance(listing) as [key, value]} | |
| <li class="flex justify-between"> | |
| <strong>{key}:</strong> | |
| <span>{value}</span> | |
| </li> | |
| {/each} | |
| </ol> | |
| {/snippet} | |
| <div class="relative"> | |
| {#if listings && listings.length > 0} | |
| {#if pagination.totalPages > 1} | |
| <div class="sticky top-16 bg-[#f5f5f5] py-2"> | |
| <Pagination {pagination} /> | |
| </div> | |
| {/if} | |
| <div | |
| class={`${ | |
| layout === 'grid' | |
| ? 'grid grid-cols-1 gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3' | |
| : 'flex flex-col' | |
| }`} | |
| > | |
| {#each listings as listing} | |
| {@const listedBy = getListedBy(listing)} | |
| <div | |
| class={`${ | |
| layout === 'grid' | |
| ? 'block rounded-lg border-2 border-gray-200 hover:bg-white' | |
| : 'mb-4 flex rounded-lg border-2 border-gray-200 hover:bg-white' | |
| }`} | |
| > | |
| <!-- Image --> | |
| <div | |
| class={`${ | |
| layout === 'grid' ? 'overflow-hidden rounded-t-lg' : 'flex rounded-2xl px-2 pt-2' | |
| }`} | |
| > | |
| <a href={listing.link}> | |
| <img | |
| src={listing.thumbnail} | |
| alt="Thumbnail" | |
| class={`${ | |
| layout === 'grid' | |
| ? 'h-48 w-full object-cover' | |
| : 'rounded-lg rounded-bl-lg border-2 object-scale-down p-1 shadow-md' | |
| }`} | |
| /> | |
| </a> | |
| </div> | |
| <!-- Listing Info --> | |
| <div class={`${layout === 'grid' ? 'p-4' : 'flex w-full flex-col'}`}> | |
| {#if layout === 'list'} | |
| <div class="flex flex-1"> | |
| <div class="w-full py-2"> | |
| <div class="float-right min-w-[250px] rounded-bl-lg border-gray-200 px-4 pb-2"> | |
| {@render listingInfo(listing)} | |
| </div> | |
| <div class="flex-1 px-2"> | |
| {truncateDescription(listing.data.PublicRemarks, 60)} | |
| </div> | |
| </div> | |
| </div> | |
| {:else} | |
| <div class="flex flex-1 flex-col"> | |
| {@render listingInfo(listing)} | |
| </div> | |
| {/if} | |
| <!-- Listed By --> | |
| <div | |
| class={`${ | |
| layout === 'grid' | |
| ? 'px-4 pb-4' | |
| : 'mt-4 mb-2 flex items-center justify-center rounded-lg px-4' | |
| }`} | |
| > | |
| <div | |
| class={`${layout === 'grid' ? 'text-sm text-gray-600' : 'flex flex-1 flex-col'}`} | |
| > | |
| {#each listedBy.agents as agent} | |
| <p> | |
| {agent.role} | |
| {agent.name} of {agent.office} | |
| </p> | |
| {/each} | |
| </div> | |
| {#if layout === 'list'} | |
| <div class="flex justify-end"> | |
| <a | |
| href={listing.link} | |
| class="w-full rounded-lg bg-black px-4 py-2 text-white sm:w-auto" | |
| > | |
| See Details | |
| </a> | |
| </div> | |
| {/if} | |
| </div> | |
| </div> | |
| </div> | |
| {/each} | |
| </div> | |
| <Pagination {pagination} /> | |
| {:else} | |
| <p>No listings found.</p> | |
| {/if} | |
| </div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { persisted } from 'svelte-persisted-store' // Cool library, but I do have my own I'm working on. | |
| const defaults = { | |
| layout: 'list' as 'list' | 'grid', | |
| sort: 'price' as 'price' | 'date', | |
| order: 'asc' as 'asc' | 'desc' | |
| } | |
| export const preferences = persisted('preferences', defaults); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment