Skip to content
55 changes: 29 additions & 26 deletions src/components/blog/BlogIndexFoundation.astro
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
---
import {
createExcerpt,
COLLECTION_INDEX_SLUG,
translatePath,
useTranslations,
buildUmamiAttrs,
getBlogThumbnail,
Expand All @@ -13,11 +11,11 @@ import type { CollectionEntry } from 'astro:content'
import TaxonomyFilter from './TaxonomyFilter.astro'
import ContentLangFilter from './ContentLangFilter.astro'
import ContentLangNotice from './ContentLangNotice.astro'
import BlogIndexHeader from './BlogIndexHeader.astro'
import BlogCard from './BlogCard.astro'

interface Props {
posts: CollectionEntry<'foundation-blog'>[]
totalEntries: number
allTerms?: string[]
enabledTerms?: string[]
selectedTerm?: string
Expand All @@ -27,6 +25,7 @@ interface Props {
}
const {
posts,
totalEntries,
allTerms = [],
enabledTerms,
selectedTerm,
Expand All @@ -40,11 +39,6 @@ const contentLangOverride =
Astro.url.pathname.includes('/lang/') || contentLang !== routeLocale
? contentLang
: undefined
const developersHref = translatePath(
'developers-blog',
routeLocale,
COLLECTION_INDEX_SLUG
)

const postAttrs = (href: string, linkText: string) =>
buildUmamiAttrs({
Expand All @@ -57,30 +51,39 @@ const postAttrs = (href: string, linkText: string) =>
})
---

<BlogIndexHeader
title={t('blog.foundation.title')}
href={developersHref}
message={t('blog.foundation.cta')}
imgHref="/img/ernie-teal.svg"
imgAlt={t('blog.header.image_alt')}
/>
<h1
class="my-xl text-neutral-900 text-h2 tablet:text-h2-md desktop:my-3xl desktop:text-h2-lg"
>
{t('blog.foundation.title')}
</h1>

<ContentLangFilter routeLocale={routeLocale} contentLang={contentLang} />
<slot name="featured" />

<ContentLangNotice routeLocale={routeLocale} contentLang={contentLang} />
<section
aria-label={t('aria.blog.filters_label')}
class="flex flex-col gap-xl mt-4xl mb-3xl tablet:my-4xl desktop:my-5xl"
>
<ContentLangFilter routeLocale={routeLocale} contentLang={contentLang} />

<TaxonomyFilter
collection="foundation-blog"
terms={allTerms}
enabledTerms={enabledTerms}
selectedTerm={selectedTerm}
href={blogIndexHref}
contentLangOverride={contentLangOverride}
/>
<TaxonomyFilter
collection="foundation-blog"
terms={allTerms}
enabledTerms={enabledTerms}
selectedTerm={selectedTerm}
href={blogIndexHref}
contentLangOverride={contentLangOverride}
/>

<ContentLangNotice
routeLocale={routeLocale}
contentLang={contentLang}
totalEntries={totalEntries}
/>
</section>

{
isTermFallback && (
<p class="mb-space-s text-step--1 text-gray-600 italic">
<p class="my-5xl text-h3 text-neutral-75 text-center tablet:my-6xl tablet:text-h3-md desktop:my-7xl desktop:text-h3-lg">
{t('blog.empty_tag_lang.message')}
</p>
)
Expand Down
126 changes: 95 additions & 31 deletions src/components/blog/ContentLangFilter.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { useTranslations, buildUmamiAttrs, stripPagination } from '@/utils'
import type { Locale } from '@/utils'
import Icon from '@/components/shared/Icon.astro'

interface Props {
routeLocale: Locale
Expand All @@ -24,11 +25,27 @@ const enHref = `${base}/lang/en`
const esHref = `${base}/lang/es`

const isEnActive = contentLang === 'en'
const isEnActiveOnEs = isEnActive && routeLocale === 'es'
const isEsActive = contentLang === 'es'

const activeClass = ['bg-primary', 'text-white']
const inactiveClass = ['bg-white']

const summaryClasses = `
list-none flex gap-xs items-center cursor-pointer
p-xs rounded-lg border border-transparent
group-open:text-neutral-100 group-open:[&_svg]:-rotate-90
[&_svg]:transition-all motion-safe:[&_svg]:duration-500 [&_svg]:ease-in-out
focus-visible:outline-none focus-visible:border focus-visible:border-primary
`
const dropdownClasses = `
list-none m-0 p-sm [&_li]:m-0
absolute top-[calc(var(--spacing-xl)+var(--spacing-xs))] z-1
bg-neutral-0 rounded-xl border border-neutral-25
`
const linkClasses = `
flex gap-md items-center px-sm py-xs rounded-lg border border-transparent
no-underline text-body-sm-standard text-neutral-75
hover:bg-neutral-25 hover:text-neutral-100
focus-visible:outline-none focus-visible:border focus-visible:border-primary
`
const btnAttrs = (lang: string) =>
buildUmamiAttrs({
pathname: Astro.url.pathname,
Expand All @@ -38,33 +55,80 @@ const btnAttrs = (lang: string) =>
})
---

<div class="mb-space-m">
<div class="font-semibold mb-3xs text-step--1">
{t('blog.lang_filter.label')}
<div
class="flex flex-col flex-wrap gap-xl text-body-sm-emphasis text-neutral-75 tablet:flex-row tablet:items-center tablet:gap-md"
>
<div class="flex flex-wrap items-center gap-md">
<details id="language-filter-details" class="group relative">
<summary class={summaryClasses}>
{t('blog.lang_filter.label')}
<Icon name="arrow-right-keyboard" class="rotate-90" />
</summary>
<ul class={dropdownClasses}>
<li>
<a
href={enHref}
class={linkClasses}
aria-current={isEnActive ? 'page' : undefined}
{...btnAttrs('en')}
>
{t('blog.lang_filter.english')}
{isEnActive && <Icon name="check" class="size-2.5" />}
</a>
</li>
<li>
<a
href={esHref}
class={linkClasses}
aria-current={isEsActive ? 'page' : undefined}
{...btnAttrs('es')}
>
{t('blog.lang_filter.spanish')}
{isEsActive && <Icon name="check" class="size-2.5" />}
</a>
</li>
</ul>
</details>
<span class="text-primary"
>{
isEsActive
? t('blog.lang_filter.spanish')
: t('blog.lang_filter.english')
}</span
>
</div>
<ul
class="flex flex-wrap gap-space-3xs list-none
[&_a]:px-space-2xs [&_a]:py-space-3xs [&_a]:text-step--1 [&_a]:rounded [&_a]:shadow [&_a]:underline [&_a]:decoration-transparent [&_a]:transition-all [&_a]:duration-300 [&_a]:hover:decoration-inherit"
>
<li>
<a
href={enHref}
class:list={isEnActive ? activeClass : inactiveClass}
aria-current={isEnActive ? 'page' : undefined}
{...btnAttrs('en')}
>
{t('blog.lang_filter.english')}
</a>
</li>
<li>
<a
href={esHref}
class:list={isEsActive ? activeClass : inactiveClass}
aria-current={isEsActive ? 'page' : undefined}
{...btnAttrs('es')}
>
{t('blog.lang_filter.spanish')}
</a>
</li>
</ul>
{
isEsActive && (
<span class="text-body-sm-standard italic">
({t('blog.lang_filter.more_en_available')})
</span>
)
}
{
isEnActiveOnEs && (
<span class="text-body-sm-standard italic">
({t('blog.lang_filter.es_available')})
</span>
)
}
</div>

<script>
const details = document.querySelector<HTMLDetailsElement>(
'#language-filter-details'
)
const summary = details?.querySelector<HTMLElement>('summary')

details?.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape' && details.open) {
details.open = false
summary?.focus()
}
})

details?.addEventListener('focusout', (e: FocusEvent) => {
if (!details.contains(e.relatedTarget as Node)) {
details.open = false
}
})
</script>
52 changes: 20 additions & 32 deletions src/components/blog/ContentLangNotice.astro
Original file line number Diff line number Diff line change
@@ -1,45 +1,33 @@
---
import { useTranslations, stripPagination } from '@/utils'
import { useTranslations } from '@/utils'
import type { Locale } from '@/utils'

interface Props {
routeLocale: Locale
contentLang: Locale
totalEntries: number
}

const { routeLocale, contentLang } = Astro.props
const { routeLocale, contentLang, totalEntries } = Astro.props
const t = useTranslations(routeLocale)
const base = stripPagination(Astro.url.pathname).replace(/\/lang\/[^/]+$/, '')
const enHref = `${base}/lang/en`

const isViewingEsOnEnPage = routeLocale === 'en' && contentLang === 'es'
const isViewingEnOnEsPage = routeLocale === 'es' && contentLang === 'en'
const showEsDisclaimer = contentLang === 'es'
const isViewingEsContent = contentLang === 'es'
const isViewingEnContent = contentLang === 'en'
---

{
isViewingEsOnEnPage && (
<aside role="note" class="text-step--1 text-gray-600 italic">
{t('blog.lang_filter.viewing_es_posts')}
</aside>
)
}

{
isViewingEnOnEsPage && (
<aside role="note" class="text-step--1 text-gray-600 italic">
{t('blog.lang_filter.viewing_en_posts')}
</aside>
)
}
<div class="flex gap-xs text-body-sm-emphasis text-primary">
<p class="m-0 text-body-sm-emphasis">
{t('blog.lang_filter.results', { totalEntries })}
</p>
{
isViewingEsContent && (
<aside role="note">({t('blog.lang_filter.viewing_es_posts')})</aside>
)
}

{
showEsDisclaimer && (
<p class="text-step--1 text-gray-600">
{t('blog.lang_filter.more_en_available')}{' '}
<a href={enHref} class="underline hover:text-primary">
{t('blog.lang_filter.view_in_en')}
</a>
</p>
)
}
{
isViewingEnContent && (
<aside role="note">({t('blog.lang_filter.viewing_en_posts')})</aside>
)
}
</div>
Loading
Loading