Skip to content

Commit

Permalink
add view transitions for PostCard everywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
nemanjam committed Jul 21, 2024
1 parent 3a9e0b0 commit fce2ba5
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 24 deletions.
5 changes: 4 additions & 1 deletion docs/working-notes/todo3.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,13 @@ open images in new tab and check all sizes
links in md colors
fix table of contents styling
retest vertical spacing, font-size and content width

// maybe
astro transitions
astro transitions, works for PostCard on blog, tags, categories, explore
--------

ask about optimal max-width image width
fix .mdx live reload
firefox has white flash bug on theme change after transitions, https://youtu.be/9MChTVlXbf8?t=363

```
2 changes: 1 addition & 1 deletion src/components/BaseHead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,5 @@ const ogImageUrl = new URL(image, baseUrl);
<script src="set-url-here" crossorigin="anonymous"></script>
-->

<ViewTransitions />
<ViewTransitions fallback="none" />
</head>
34 changes: 30 additions & 4 deletions src/components/PostCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import TagList from '@/components/TagList.astro';
import { draftText } from '@/constants/data';
import { IMAGE_SIZES } from '@/constants/image';
import { ROUTES } from '@/constants/routes';
import { setTransitionSlug, TRANSITION_ELEMENT_IDS } from '@/constants/transitions';
import { formatDate, formatDateIso } from '@/utils/datetime';
import { cn } from '@/utils/styles';
Expand Down Expand Up @@ -39,6 +40,8 @@ const { lastAccessDate, isUpdatedDate } = getPublishedOrUpdatedDate({
publishDate,
updatedDate,
});
const getTransitionNameFromElementId = setTransitionSlug({ pageSlug: slug });
---

<article
Expand All @@ -58,7 +61,9 @@ const { lastAccessDate, isUpdatedDate } = getPublishedOrUpdatedDate({
alt={heroAlt}
itemprop="image"
class="w-full h-[168px] xs:h-[250px] md:h-[168px] md:max-w-[298px] object-cover rounded-box"
transition:name={`hero-image-${slug}`}
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.HERO_IMAGE,
})}
/>
</div>
)
Expand All @@ -67,7 +72,12 @@ const { lastAccessDate, isUpdatedDate } = getPublishedOrUpdatedDate({
{/* right column */}
<div class={cn('flex flex-col', { 'md:basis-2/3': !noHero })}>
{/* category and publishDate row*/}
<div class="flex items-start justify-between mb-4 text-sm leading-none">
<div
class="flex items-start justify-between mb-4 text-sm leading-none"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.META,
})}
>
<Link
href={`${ROUTES.CATEGORIES}${category}`}
class="inline-flex items-center gap-2 bg-primary-base-200 font-medium px-2 py-0.5 rounded-button"
Expand All @@ -87,14 +97,30 @@ const { lastAccessDate, isUpdatedDate } = getPublishedOrUpdatedDate({
</div>

{/* title */}
<h2 class="text-2xl font-bold break-words md:line-clamp-2 mt-0 mb-2">
<h2
class="text-2xl font-bold break-words md:line-clamp-2 mt-0 mb-2"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.TITLE,
})}
>
<Link href={`${ROUTES.BLOG}${slug}`} variant="link-heading">
{title}
{draft && <sup class="text-sm text-red-500 ml-1">{draftText}</sup>}
</Link>
</h2>

{description && <p class="text-base mb-4 line-clamp-2">{description}</p>}
{
description && (
<p
class="text-base mb-4 line-clamp-2"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.DESCRIPTION,
})}
>
{description}
</p>
)
}

{/* reading time and read more link */}
<div class="flex items-center xs:items-end justify-between">
Expand Down
33 changes: 30 additions & 3 deletions src/components/PostCardSmall.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Link from '@/components/Link.astro';
import { draftText } from '@/constants/data';
import { IMAGE_SIZES } from '@/constants/image';
import { ROUTES } from '@/constants/routes';
import { setTransitionSlug, TRANSITION_ELEMENT_IDS } from '@/constants/transitions';
import { formatDate, formatDateIso } from '@/utils/datetime';
import { cn } from '@/utils/styles';
Expand All @@ -25,6 +26,8 @@ const { lastAccessDate, isUpdatedDate } = getPublishedOrUpdatedDate({
publishDate,
updatedDate,
});
const getTransitionNameFromElementId = setTransitionSlug({ pageSlug: slug });
---

<article
Expand All @@ -40,16 +43,40 @@ const { lastAccessDate, isUpdatedDate } = getPublishedOrUpdatedDate({
src={heroImage}
alt={heroAlt}
class="object-cover rounded-box hidden xs:block"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.HERO_IMAGE,
})}
/>
<div>
<h4 class="b-h4 text-xl leading-none line-clamp-1 mb-1">
<h4
class="b-h4 text-xl leading-none line-clamp-1 mb-1"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.TITLE,
})}
>
<Link variant="link-heading" href={`${ROUTES.BLOG}${slug}`}>
{draft && <sup class="text-sm text-red-500 mr-1">{draftText}</sup>}
{title}
</Link>
</h4>
{description && <p class="text-base text-captions line-clamp-1 mb-2">{description}</p>}
<span class="inline-flex items-center gap-1 text-sm text-captions text-nowrap">
{
description && (
<p
class="text-base text-captions line-clamp-1 mb-2"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.DESCRIPTION,
})}
>
{description}
</p>
)
}
<span
class="inline-flex items-center gap-1 text-sm text-captions text-nowrap"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.META,
})}
>
<Icon name={isUpdatedDate ? 'mdi:edit-outline' : 'mdi:access-time'} class="w-4 h-4" />
<time itemprop="datePublished" datetime={formatDateIso(lastAccessDate)}>
{formatDate(lastAccessDate)}
Expand Down
1 change: 1 addition & 0 deletions src/components/ThemeScript.astro
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import * as themeConstants from '@/constants/themes';
// initial setup
setTheme(getUserPreference());

// fails in firefox
// View Transitions hook to restore theme
document.addEventListener('astro:after-swap', () => setTheme(getUserPreference()));

Expand Down
3 changes: 2 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const configData: ConfigType = {
SITE_URL: process.env.SITE_URL,
SITE_TITLE: 'Nemanja Mitic',
SITE_DESCRIPTION: 'I am Nemanja, full stack developer',
PAGE_SIZE: { POST_CARD: 3, POST_CARD_SMALL: 10 },
PAGE_SIZE_POST_CARD: 3,
PAGE_SIZE_POST_CARD_SMALL: 10,
MORE_POSTS_COUNT: 3,
AUTHOR_NAME: 'Nemanja Mitic',
AUTHOR_EMAIL: '[email protected]',
Expand Down
28 changes: 28 additions & 0 deletions src/constants/transitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export interface TransitionNameArgs {
elementId: string;
pageSlug: string;
}

export type TransitionElementId = Pick<TransitionNameArgs, 'elementId'>;
export type TransitionPageSlug = Pick<TransitionNameArgs, 'pageSlug'>;

export const getTransitionName = ({ elementId, pageSlug }: TransitionNameArgs): string =>
`${elementId}-${pageSlug}`;

/** apply only slug, returns getTransitionNameFromElementId({elementId}) function */
export const setTransitionSlug =
({ pageSlug }: TransitionPageSlug) =>
({ elementId }: TransitionElementId) =>
getTransitionName({ elementId, pageSlug });

export const TRANSITION_ELEMENT_IDS = {
POST_CARD: {
HERO_IMAGE: 'post-card-hero-image',
TITLE: 'post-card-title',
DESCRIPTION: 'post-card-description',
META: 'post-card-meta',
},
EXPLORE: {
YEAR: 'explore-year',
},
} as const;
2 changes: 1 addition & 1 deletion src/pages/blog/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { GetStaticPathsOptions } from 'astro';
// [page].astro and getStaticPaths because of pagination
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
const posts: Post[] = await getAllPostsWithReadingTime();
const pageSize = CONFIG.PAGE_SIZE.POST_CARD; // must take entire config here, interesting
const pageSize = CONFIG.PAGE_SIZE_POST_CARD; // must take entire config here, interesting
const pagination = paginate(posts, { pageSize });
pagination.push({ params: { page: '1' }, props: pagination[0].props });
Expand Down
30 changes: 26 additions & 4 deletions src/pages/blog/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import TagList from '@/components/TagList.astro';
import { draftText } from '@/constants/data';
import { IMAGE_SIZES } from '@/constants/image';
import { ROUTES } from '@/constants/routes';
import { setTransitionSlug, TRANSITION_ELEMENT_IDS } from '@/constants/transitions';
import { CONFIG } from '@/config';
import { getOpenGraphImagePath } from '@/libs/api/open-graph/image-path';
Expand Down Expand Up @@ -72,6 +73,8 @@ const shareProps = {
url: `${SITE_URL}${ROUTES.BLOG}${slug}`,
};
const getTransitionNameFromElementId = setTransitionSlug({ pageSlug: slug });
// handle all metadata here
const image = getOpenGraphImagePath(pathname);
Expand All @@ -91,25 +94,44 @@ const metadata: Metadata = { title, description, image };
loading="eager"
itemprop="image"
class="block max-w-full h-auto aspect-[16/8] object-cover"
transition:name={`hero-image-${slug}`}
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.HERO_IMAGE,
})}
/>
)
}
</Fragment>

<Fragment slot="hero-text">
<h1 class="b-h1">
<h1
class="b-h1"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.TITLE,
})}
>
{title}
{draft && <sup class="text-red-500 ml-1">{draftText}</sup>}
</h1>

{
description && (
<p class="font-normal text-content text-xl md:text-2xl mb-6 md:mb-8">{description}</p>
<p
class="font-normal text-content text-xl md:text-2xl mb-6 md:mb-8"
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.DESCRIPTION,
})}
>
{description}
</p>
)
}

<PostMeta {...postMetaProps} />
<PostMeta
{...postMetaProps}
transition:name={getTransitionNameFromElementId({
elementId: TRANSITION_ELEMENT_IDS.POST_CARD.META,
})}
/>
</Fragment>

<Fragment slot="content">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/blog/categories/[category]/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { GetStaticPathsOptions } from 'astro';
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
const posts: Post[] = await getAllPostsWithReadingTime();
const pageSize = CONFIG.PAGE_SIZE.POST_CARD;
const pageSize = CONFIG.PAGE_SIZE_POST_CARD;
const uniqueCategories = getUniqueCategories(posts);
Expand Down
13 changes: 11 additions & 2 deletions src/pages/blog/explore/[...filter]/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getUniqueTags } from '@/modules/post/tag';
import List from '@/layouts/List.astro';
import Explore from '@/components/Explore.astro';
import PostCardSmall from '@/components/PostCardSmall.astro';
import { getTransitionName, TRANSITION_ELEMENT_IDS } from '@/constants/transitions';
import { CONFIG } from '@/config';
import { getPageMetadata } from '@/utils/metadata';
import { pickPaginationPropsFromPage } from '@/utils/pagination';
Expand All @@ -16,7 +17,7 @@ import type { GetStaticPathsOptions } from 'astro';
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
const posts: Post[] = await getAllPostsWithReadingTime();
const pageSize = CONFIG.PAGE_SIZE.POST_CARD_SMALL;
const pageSize = CONFIG.PAGE_SIZE_POST_CARD_SMALL;
// grouping is outside of getStaticPaths
Expand Down Expand Up @@ -92,7 +93,15 @@ const postsByYear = groupPostsByYear(paginatedPosts);
{
postsByYear.years.map((year) => (
<>
<h2 class="b-h2">{year}</h2>
<h2
class="b-h2"
transition:name={getTransitionName({
elementId: TRANSITION_ELEMENT_IDS.EXPLORE.YEAR,
pageSlug: year,
})}
>
{year}
</h2>
<div>
{postsByYear.posts[year].map((post) => (
<PostCardSmall {post} class="last:mb-6" />
Expand Down
2 changes: 1 addition & 1 deletion src/pages/blog/tags/[tag]/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { GetStaticPathsOptions } from 'astro';
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
const posts: Post[] = await getAllPostsWithReadingTime();
const pageSize = CONFIG.PAGE_SIZE.POST_CARD;
const pageSize = CONFIG.PAGE_SIZE_POST_CARD;
const uniqueTags = getUniqueTags(posts);
Expand Down
2 changes: 1 addition & 1 deletion src/pages/projects/[...page].astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { GetStaticPathsOptions, Page } from 'astro';
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
const sortedProjects = await getAllProjects();
const pageSize = CONFIG.PAGE_SIZE.POST_CARD;
const pageSize = CONFIG.PAGE_SIZE_POST_CARD;
const pagination = paginate(sortedProjects, { pageSize });
pagination.push({ params: { page: '1' }, props: pagination[0].props });
Expand Down
6 changes: 2 additions & 4 deletions src/schemas/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ export const configSchema = z.object({
SITE_URL: z.string().url().regex(/[^/]$/, 'SITE_URL should not end with a slash "/"'),
SITE_TITLE: z.string().min(1),
SITE_DESCRIPTION: z.string().min(1),
PAGE_SIZE: z.object({
POST_CARD: z.number(),
POST_CARD_SMALL: z.number(),
}),
PAGE_SIZE_POST_CARD: z.number(),
PAGE_SIZE_POST_CARD_SMALL: z.number(),
MORE_POSTS_COUNT: z.number(),
AUTHOR_NAME: z.string().min(1),
AUTHOR_EMAIL: z.string().email(),
Expand Down

0 comments on commit fce2ba5

Please sign in to comment.