🛠️ build(astro): nuxtjs to astro

This commit is contained in:
Eshan Roy (Eshanized)
2024-06-16 23:19:02 +05:30
parent 8f7130024e
commit ec3cafabd8
58 changed files with 12225 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
<div class="container">
<div class="call-to-action mt-24 mb-32 flex flex-col items-center gap-12 rounded-xl p-12 md:p-24">
<h2 class="text-center text-3xl md:text-5xl">Get this theme on GitHub</h2>
<a href="https://github.com/markteekman/accessible-astro-starter" class="text-center text-lg"> Use this theme</a>
</div>
</div>
<style lang="scss">
.call-to-action {
color: var(--neutral-900);
background-image: linear-gradient(40deg, var(--primary-100), var(--secondary-200));
}
.call-to-action a:not(button),
.call-to-action a:not(button):visited {
padding: 1rem;
color: var(--neutral-900);
font-weight: bold;
border: 3px solid var(--neutral-900);
border-radius: 3px;
text-decoration: none;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out;
&:where(:hover, :focus) {
color: var(--neutral-100);
background-color: var(--neutral-900);
text-decoration: underline;
}
}
</style>

View File

@@ -0,0 +1,17 @@
---
import { Media } from 'accessible-astro-components'
const { imgSrc, reverseImg = false } = Astro.props
---
<section class="my-64">
<div class="container">
<div class="grid grid-cols-1 gap-24 md:grid-cols-2">
{!reverseImg ? <Media class="rounded-lg" src={imgSrc} /> : ''}
<div class="space-content flex flex-col justify-center">
<slot />
</div>
{reverseImg ? <Media class="rounded-lg" src={imgSrc} /> : ''}
</div>
</div>
</section>

View File

@@ -0,0 +1,15 @@
---
const { count, title, sub } = Astro.props
---
<div class="animate text-center">
<p class="text-6xl font-bold">{count}</p>
<p class="text-4xl font-semibold">{title}</p>
<p class="text-2xl">{sub}</p>
</div>
<style lang="scss">
div > p:first-child {
color: var(--action-color);
}
</style>

View File

@@ -0,0 +1,67 @@
---
import { Icon } from 'astro-icon/components'
const { icon = 'mdi:rocket', title = 'Title' } = Astro.props
---
<div class="feature flex flex-col gap-4">
<Icon name={icon} />
<div class="content">
<h3>{title}</h3>
<p>
<slot>Lorem ipsum dolor sit amet consectetur adipisicing elit. Hic, corporis.</slot>
</p>
</div>
</div>
<style lang="scss">
@use '../assets/scss/base/breakpoint' as *;
.feature {
position: relative;
width: calc(100% - 0.5rem);
padding: 2rem;
> * {
position: relative;
z-index: 2;
}
@include breakpoint(large) {
width: 100%;
}
&::before,
&::after {
content: '';
position: absolute;
}
&::before {
inset: 0;
background-color: var(--neutral-100);
border: 3px solid var(--neutral-700);
border-radius: 1rem;
box-shadow: 0 0 0 6px var(--neutral-100);
z-index: 1;
}
&::after {
background-color: var(--action-color);
inset: 1rem -0.85rem -0.85rem 1rem;
border-radius: 1rem;
z-index: 0;
}
}
:global(.feature [data-icon]) {
height: auto;
width: 4rem;
color: var(--action-color);
}
:global(.darkmode .feature::before) {
background-color: var(--dark-100);
box-shadow: 0 0 0 6px var(--dark-100);
}
</style>

View File

@@ -0,0 +1,18 @@
---
import CallToAction from './CallToAction.astro'
const currentYear = new Date().getFullYear()
---
<footer>
<CallToAction />
<section class="py-8">
<div class="container">
<p>
&copy; {currentYear} - Starter Theme for <a href="https://astro.build/">Astro</a>. Made with ❤️ by <a
href="https://github.com/markteekman">Mark Teekman</a
>.
</p>
</div>
</section>
</footer>

View File

@@ -0,0 +1,65 @@
---
import Navigation from '../components/Navigation.astro'
import { SkipLinks } from 'accessible-astro-components'
import { Icon } from 'astro-icon/components'
---
<header>
<SkipLinks />
<Navigation>
<li class="menu-item">
<a href="/">Home</a>
</li>
<li class="menu-item">
<a href="/blog/">Blog</a>
</li>
<li class="menu-item has-dropdown">
<button aria-haspopup="true" aria-expanded="false">Example Pages</button>
<ul class="dropdown-menu">
<li class="submenu-item">
<a href="/mdx-page/">MDX Page</a>
</li>
<li class="submenu-item">
<a href="/markdown-page/">Markdown Page</a>
</li>
<li class="submenu-item">
<a href="/accessible-components">Accessible Components</a>
</li>
</ul>
</li>
<li class="menu-item">
<a href="https://accessible-astro.dev" title="external link" rel="external noopener noreferrer">External Link</a>
</li>
<li class="menu-item type-icon">
<a href="https://github.com/markteekman/accessible-astro-starter" title="Go to the GitHub page">
<Icon name="ion:logo-github" />
</a>
</li>
</Navigation>
</header>
<style lang="scss" is:global>
@use '../assets/scss/base/outline' as *;
header {
.type-icon a {
display: block;
[data-icon] {
height: auto;
margin-top: -4px;
width: 30px;
path {
fill: var(--action-color);
}
}
&:hover {
[data-icon] path {
fill: var(--action-color-state);
}
}
}
}
</style>

47
src/components/Hero.astro Normal file
View File

@@ -0,0 +1,47 @@
---
import { Icon } from 'astro-icon/components'
const { src = '/astronaut-hero-img.webp' } = Astro.props
---
<section class="hero my-24">
<div class="container">
<div class="grid grid-cols-1 items-center gap-24 lg:grid-cols-2">
<div class="flex flex-col items-center gap-8 md:items-start">
<h1 class="text-center text-6xl md:text-left lg:text-8xl">
<slot><span class="text-gradient">Accessible</span> Starter for Astro</slot>
</h1>
<div class="flex flex-col gap-3 min-[500px]:flex-row">
<a class="button has-icon" href="https://github.com/markteekman/accessible-astro-starter">
<Icon name="ion:star-outline" />
Star on GitHub
</a>
<a
class="button has-icon color-secondary"
href="https://github.com/markteekman/accessible-astro-starter/blob/main/README.md"
>
<Icon name="ion:bookmark-outline" />
Read the Docs
</a>
</div>
</div>
<img class="hidden lg:block" src={src} alt="" decoding="async" />
</div>
</div>
</section>
<style lang="scss">
.text-gradient {
background: linear-gradient(315deg, var(--primary-200) 25%, var(--secondary-500));
background-clip: border-box;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
:global(.darkmode .text-gradient) {
background: linear-gradient(315deg, var(--primary-200) 25%, var(--secondary-400));
background-clip: border-box;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>

View File

@@ -0,0 +1,415 @@
---
import ResponsiveToggle from './ResponsiveToggle.astro'
import { DarkMode } from 'accessible-astro-components'
import { Image } from 'astro:assets'
import logo from '../assets/img/logo.svg'
---
<div id="main-navigation" class="is-desktop py-8">
<div class="container">
<a href="/" class="flex items-center gap-2 !no-underline">
<Image src={logo} alt="Your Logo" width="47" height="37" />
<span class="font-bold">Accessible Astro</span>
</a>
<div class="wrapper">
<nav class="desktop-menu" aria-label="Main navigation desktop">
<ul class="menu">
<slot />
</ul>
</nav>
<DarkMode />
<ResponsiveToggle />
</div>
<nav class="mobile-menu" aria-label="Main navigation mobile">
<ul class="menu">
<slot />
</ul>
</nav>
</div>
</div>
<script>
document.addEventListener('astro:page-load', () => {
// variables
const mainNav = document.querySelector('#main-navigation')
const mainMenu = mainNav.querySelector('ul')
const dropdownMenus = [...document.querySelectorAll('.has-dropdown button')]
// functions
const setActiveMenuItem = () => {
const mobileDesktopMenus = mainNav.querySelectorAll('nav > ul')
const currenPathname = window.location.pathname
mobileDesktopMenus.forEach((menu) => {
const menuItems = [...menu.querySelectorAll('a:not([rel*="external"])')] as HTMLAnchorElement[]
menuItems.forEach((menuItem) => {
if (currenPathname.includes(menuItem.pathname.replaceAll('/', '')) && menuItem.textContent !== 'Home') {
menuItem.classList.add('is-active')
menuItem.setAttribute('aria-current', 'page')
} else if (menuItem.textContent === 'Home' && currenPathname === '/') {
menuItem.classList.add('is-active')
menuItem.setAttribute('aria-current', 'page')
}
})
})
}
const checkMenuSize = () => {
const mainNavWidth = mainNav.getBoundingClientRect().width
const desktopMenuWidth = document.querySelector('.desktop-menu').getBoundingClientRect().width
const logoWidthBuffer = 300
const totalMenuWidth = Math.round(desktopMenuWidth) + logoWidthBuffer
if (totalMenuWidth >= mainNavWidth) {
mainNav.classList.remove('is-desktop')
mainNav.classList.add('is-mobile')
} else if (totalMenuWidth <= mainNavWidth) {
mainNav.classList.add('is-desktop')
mainNav.classList.remove('is-mobile')
}
}
const isOutOfViewport = (element) => {
const elementBounds = element.getBoundingClientRect()
return elementBounds.right > (window.innerWidth || document.documentElement.clientWidth)
}
const openDropdownMenu = (dropdownMenu) => {
const dropdownList = dropdownMenu.parentNode.querySelector('ul')
dropdownMenu.classList.add('show')
dropdownMenu.setAttribute('aria-expanded', 'true')
if (isOutOfViewport(dropdownList)) {
dropdownList.style.left = 'auto'
}
}
const closeDropdownMenu = (dropdownMenu) => {
dropdownMenu.classList.remove('show')
dropdownMenu.setAttribute('aria-expanded', 'false')
}
const closeAllDropdownMenus = () => {
for (let i = 0; i < dropdownMenus.length; i++) {
closeDropdownMenu(dropdownMenus[i])
}
}
const toggleDropdownMenu = (event) => {
if (event.target.getAttribute('aria-expanded') === 'false') {
closeAllDropdownMenus()
openDropdownMenu(event.target)
} else {
closeDropdownMenu(event.target)
}
}
// execution
mainMenu &&
mainMenu.addEventListener('keydown', (event) => {
const element = event.target as Element
const currentMenuItem = element.closest('li')
const menuItems = [...mainMenu.querySelectorAll('.menu-item')]
const currentDropdownMenu = element.closest('.has-dropdown button')
const currentDropdownMenuItem = element.closest('.has-dropdown li')
const currentIndex = menuItems.findIndex((item) => item === currentMenuItem)
const key = event.key
let targetItem
if (key === 'ArrowRight') {
if (menuItems.indexOf(currentMenuItem) === menuItems.length - 1) {
targetItem = menuItems[0]
} else {
targetItem = menuItems[currentIndex + 1]
}
}
if (key === 'ArrowLeft') {
if (menuItems.indexOf(currentMenuItem) === 0) {
targetItem = menuItems[menuItems.length - 1]
} else {
targetItem = menuItems[currentIndex - 1]
}
}
if (key === 'Escape') {
targetItem = menuItems[0]
}
if (currentDropdownMenu) {
const firstDropdownItem = currentDropdownMenu.nextElementSibling.querySelector('li')
if (key === 'ArrowDown') {
event.preventDefault()
openDropdownMenu(currentDropdownMenu)
targetItem = firstDropdownItem
}
if (key === 'Escape') {
closeDropdownMenu(currentDropdownMenu)
}
}
if (currentDropdownMenuItem) {
const currentDropdownList = currentDropdownMenuItem.parentNode
const dropdownMenuItems = [...currentDropdownList.querySelectorAll('li')]
const currentIndex = dropdownMenuItems.findIndex((item) => item === currentDropdownMenuItem)
if (key === 'ArrowDown') {
event.preventDefault()
if (dropdownMenuItems.indexOf(currentDropdownMenuItem as HTMLLIElement) === dropdownMenuItems.length - 1) {
targetItem = dropdownMenuItems[0]
} else {
targetItem = dropdownMenuItems[currentIndex + 1]
}
}
if (key === 'ArrowUp') {
event.preventDefault()
if (dropdownMenuItems.indexOf(currentDropdownMenuItem as HTMLLIElement) === 0) {
targetItem = dropdownMenuItems[dropdownMenuItems.length - 1]
} else {
targetItem = dropdownMenuItems[currentIndex - 1]
}
}
if (key === 'Escape') {
const currentDropdownMenu = (currentDropdownList as Element).previousElementSibling
targetItem = currentDropdownMenu.parentNode
closeAllDropdownMenus()
}
if (key === 'Tab') {
const currentDropdownMenu = (currentDropdownList as Element).previousElementSibling
if (dropdownMenuItems.indexOf(currentDropdownMenuItem as HTMLLIElement) === dropdownMenuItems.length - 1) {
closeDropdownMenu(currentDropdownMenu)
}
}
}
if (targetItem) {
targetItem.querySelector('a, button, input').focus()
}
})
dropdownMenus &&
dropdownMenus.forEach((dropdownMenu) => {
dropdownMenu.addEventListener('click', toggleDropdownMenu)
})
setActiveMenuItem()
checkMenuSize()
window.addEventListener('resize', checkMenuSize)
window.addEventListener('click', (event) => {
const element = event.target as Element
if (!element.hasAttribute('aria-haspopup') && !element.classList.contains('submenu-item')) {
closeAllDropdownMenus()
}
})
})
</script>
<style lang="scss" is:global>
@use '../assets/scss/base/breakpoint' as *;
@use '../assets/scss/base/outline' as *;
#main-navigation {
> .container {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
&.is-desktop {
.desktop-menu {
visibility: visible;
position: static;
}
.mobile-menu {
display: none;
}
.darkmode-toggle {
margin-top: -6px;
}
}
&.is-mobile {
flex-direction: column;
.mobile-menu {
display: none;
&.show {
display: block;
}
}
.desktop-menu {
visibility: hidden;
z-index: -99;
position: absolute;
left: 0;
}
.responsive-toggle {
display: block;
}
}
.wrapper {
display: flex;
align-items: center;
gap: 1rem;
}
nav {
> ul {
display: flex;
gap: 1.5rem;
list-style-type: none;
a,
button {
text-decoration: none;
font-size: 1.125rem;
line-height: 1.6875rem;
}
a:hover,
a:focus,
.is-active,
.has-dropdown > button:hover,
.has-dropdown > button:focus {
text-decoration: underline;
text-decoration-thickness: 1px;
text-decoration-style: wavy;
text-underline-offset: 7px;
}
.is-active {
font-weight: bold;
}
}
}
.mobile-menu {
flex-basis: 100%;
padding: 2rem 0;
> ul {
flex-direction: column;
ul {
position: relative;
margin-top: 1rem;
}
}
a,
button {
display: block;
width: 100%;
padding: 0.5rem 0;
}
}
.has-dropdown {
position: relative;
> button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0;
margin-top: -1px;
border: none;
color: var(--action-color);
&:hover {
color: var(--action-color-state);
&::after {
border-color: var(--action-color-state);
}
}
&::after {
content: '';
width: 0.85rem;
height: 0.75em;
margin-top: -0.25rem;
border-style: solid;
border-width: 0.2em 0.2em 0 0;
border-color: var(--action-color);
transform: rotate(135deg);
}
&.show {
&::after {
margin-top: 0.25rem;
transform: rotate(-45deg);
}
~ ul {
display: flex;
flex-direction: column;
gap: 1rem;
}
}
}
ul {
display: none;
position: absolute;
z-index: 100;
min-width: 260px;
top: 125%;
right: 0;
bottom: auto;
left: 0;
padding: 1rem;
background-color: var(--neutral-background);
border: 2px solid black;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
}
}
}
.darkmode-toggle {
padding: 0;
border: none;
svg {
width: 30px;
margin-top: 4px;
}
svg path {
fill: var(--action-color);
}
&:hover {
svg path {
fill: var(--action-color-state);
}
}
&:focus {
@include outline;
&:not(:focus-visible) {
outline: none;
box-shadow: none;
}
}
}
</style>

View File

@@ -0,0 +1,64 @@
<button class="responsive-toggle" aria-expanded="false" aria-label="Open menu navigation">
<svg width="26" height="21" aria-hidden="true" fill="var(--action-color)" xmlns="http://www.w3.org/2000/svg"
><path
d="M2 1.667h24m-24 8h24m-24 8h24"
stroke="var(--action-color)"
stroke-width="2.667"
stroke-linecap="round"
stroke-linejoin="round"></path></svg
>
</button>
<script>
document.addEventListener('astro:page-load', () => {
// variables
const responsiveToggle = document.querySelector('.responsive-toggle')
// functions
const openMenu = (toggle) => {
toggle.setAttribute('aria-expanded', true)
toggle.setAttribute('aria-label', 'Close menu navigation')
toggle.innerHTML = `<svg width="20" height="20" aria-hidden="true" fill="var(--action-color)" xmlns="http://www.w3.org/2000/svg"><path d="M10 10 2 2m8 8 8 8m-8-8 8-8m-8 8-8 8" stroke="var(--action-color)" stroke-width="2.667" stroke-linecap="round" stroke-linejoin="round"/></svg>`
}
const closeMenu = (toggle) => {
toggle.setAttribute('aria-expanded', false)
toggle.setAttribute('aria-label', 'Open menu navigation')
toggle.innerHTML = `<svg width="26" height="21" aria-hidden="true" fill="var(--action-color)" xmlns="http://www.w3.org/2000/svg"><path d="M2 1.667h24m-24 8h24m-24 8h24" stroke="var(--action-color)" stroke-width="2.667" stroke-linecap="round" stroke-linejoin="round"/></svg>`
}
// execution
responsiveToggle.addEventListener('click', (_) => {
const mobileNavigation = document.querySelector('.mobile-menu')
mobileNavigation.classList.toggle('show')
mobileNavigation.classList.contains('show') ? openMenu(responsiveToggle) : closeMenu(responsiveToggle)
})
})
</script>
<style lang="scss">
@use '../assets/scss/base/breakpoint' as *;
@use '../assets/scss/base/outline' as *;
.responsive-toggle {
display: none;
padding: 0;
margin-top: 6px;
border: none;
svg {
width: 30px;
path {
transition: fill 0.2s ease-in-out;
}
}
&:hover {
svg path {
fill: var(--primary-400);
}
}
}
</style>

View File

@@ -0,0 +1,26 @@
---
import { ViewTransitions } from 'astro:transitions'
const { title, description, url, image, author } = Astro.props
let subtitle = 'Accessible Astro Starter'
---
<!-- general meta -->
<meta name="title" content={`${title} - ${subtitle}`} />
<meta name="description" content={description} />
<meta name="author" content={author} />
<!-- open graph -->
<meta property="og:title" content={`${title} - ${subtitle}`} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={url} />
<meta property="og:image" content={Astro.site ? `${Astro.site}${image}` : image} />
<!-- twitter card -->
<!-- page title -->
<title>{title} - {subtitle}</title>
<ViewTransitions />