feat: external links now open in new tab + MDXAccordion component

This commit is contained in:
Kebo Kitanari 2024-11-14 13:46:59 -06:00
parent 5f8a34cbd3
commit 06e76d38df
4 changed files with 157 additions and 0 deletions

View file

@ -2,6 +2,7 @@ import HR from './components/HR.astro'
import MDXImage from './components/mdx/MDXImage.astro'
import MDXCallout from './components/mdx/MDXCallout.astro'
import MDXCodeBlock from './components/mdx/MDXCodeBlock.astro'
import MDXLink from './components/mdx/MDXLink.astro'
export const components = {
hr: HR,
@ -9,4 +10,5 @@ export const components = {
MDXImage: MDXImage,
MDXCallout: MDXCallout,
pre: MDXCodeBlock,
a: MDXLink,
}

View file

@ -0,0 +1,126 @@
---
// I stole ALLLLLL of this from https://webreaper.dev/posts/astro-accessible-accordion/
interface Props {
title: string;
}
const { title } = Astro.props as Props;
import HR from "../HR.astro";
---
<noscript>
<h2 class="text-xl text-title font-serif">
{ title }
<svg
class="inline-flex h-7 w-7"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
><path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m6 9l6 6l6-6"></path></svg
>
</h2>
<slot/>
</noscript>
<div class="accordion group relative mb-2 rounded-md border border-crusta-900 dark:border-indigo-400 hidden">
<button
class="accordion__button flex w-full flex-1 items-center justify-between gap-2 p-3 text-left font-medium transition hover:text-subtitle sm:px-4"
type="button"
id={`${title} accordion menu button`}
aria-expanded="false"
aria-controls={`${title} accordion menu content`}
>
<span class="text-xl font-serif">{title}</span>
<!-- if using astro and the astro-icon package
<Icon
name="tabler:chevron-down"
aria-hidden="true"
class="accordion__chevron h-7 w-7 shrink-0 transition-transform"
/>
-->
<!-- use this is not using astro-icon (or another SVG you like) -->
<svg
class="accordion__chevron h-7 w-7 shrink-0 transition-transform"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
><path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m6 9l6 6l6-6"></path></svg
>
</button>
<div
id={`${title} accordion menu content`}
aria-labelledby={`${title} accordion menu button`}
class="accordion__content hidden max-h-0 overflow-hidden px-3 transition-all duration-300 ease-in-out sm:px-4"
>
<HR class="mt-0"/>
<div class="prose mb-4 items-center mt-1 max-w-full transition-[height]">
<slot/>
</div>
</div>
</div>
<script>
function accordionSetup() {
const menus = document.querySelectorAll(".accordion") as NodeListOf<HTMLElement>; // set this up on each menu
menus.forEach((menu) => {
menu.classList.remove("hidden");
const button = menu.querySelector(".accordion__button") as HTMLElement; // the clickable banner
const chevron = menu.querySelector(".accordion__chevron") as HTMLElement; // the chevron icon that animates
const content = menu.querySelector(".accordion__content") as HTMLElement; // the stuff that's revealed when open
if (button && content && chevron) {
button.addEventListener("click", (event) => {
if (!menu.classList.contains("active")) { // if closed, stop having it be closed!
menu.classList.add("active");
button.setAttribute("aria-expanded", true);
// we need to set the max height to the height of the accordion object so the animations work correctly
content.classList.remove("hidden");
content.style.maxHeight = content.scrollHeight + "px";
chevron.classList.add("rotate-180");
} else { // if open, stop having it be open!!!!
menu.classList.remove("active");
button.setAttribute("aria-expanded", false);
content.style.maxHeight = '0px';
chevron.classList.remove("rotate-180");
// make text invisible after animation
setTimeout(() => { content.classList.add("hidden") }, 300);
}
event.preventDefault();
return false;
})
}
})
}
accordionSetup();
document.addEventListener("astro:after-swap", accordionSetup);
</script>

View file

@ -0,0 +1,28 @@
---
import Icon from '../Icon.astro'
interface Props {
href: string
class: string
}
const p = Astro.props as Props;
let external = false;
if (p.href.includes('https://') || p.href.includes('http://')) {
external = true;
}
---
<a href={p.href} class={p.class} target={external ? '_blank' : '_self'}>
<slot/>
{external ? (
<>
<Icon icon="FaExternalLink" class='w-2.5 inline-block align-super fill-crusta-800 fill:stroke-night-400' aria-hidden="true"/>
<span class="hidden">opens in a new tab</span>
</>
) : ''}
</a>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l82.7 0L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3l0 82.7c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160c0-17.7-14.3-32-32-32L320 0zM80 32C35.8 32 0 67.8 0 112L0 432c0 44.2 35.8 80 80 80l320 0c44.2 0 80-35.8 80-80l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 112c0 8.8-7.2 16-16 16L80 448c-8.8 0-16-7.2-16-16l0-320c0-8.8 7.2-16 16-16l112 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L80 32z"/></svg>

After

Width:  |  Height:  |  Size: 672 B