Thanks to visit codestin.com
Credit goes to github.com

Skip to content

webstackdev/astro.webstackbuilders.com

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Astro Starter Kit: Basics

@TODO: See NOTES.md, this file is split between here and there. Consider using Nightwatch instead of Playwright for e2e testing.

@TODO: See here for utility components like shortcodes: https://docs.astro.build/en/reference/api-reference/#astroslotsrender

MDX Components

The project includes custom components that can be used in MDX files for enhanced content:

Newsletter Signup Component

Use the <Signup> component to add a newsletter signup form to your MDX content:

<Signup
  title="Stay Updated"
  description="Get the latest articles and insights delivered to your inbox."
  buttonText="Subscribe"
  placeholder="Enter your email"
/>

Props:

  • title (optional): Heading text for the signup form (default: "Newsletter Signup")
  • description (optional): Description text below the title (default: "Subscribe to get the latest updates and insights.")
  • buttonText (optional): Text for the submit button (default: "Subscribe")
  • placeholder (optional): Placeholder text for the email input (default: "Enter your email address")
  • className (optional): Additional CSS classes to apply

Tweet Embed Component

Use the <X> component to embed tweets in your MDX content:

<X
  id="1234567890123456789"
  author="username"
  content="This is the tweet content..."
  date="2024-01-15"
  avatar="/path/to/avatar.jpg"
/>

Props:

  • id (required): The tweet ID from the Twitter/X URL
  • author (optional): Twitter username (default: "Twitter User")
  • content (optional): Tweet text content (default: "Loading tweet...")
  • date (optional): Tweet date in YYYY-MM-DD format (default: current date)
  • avatar (optional): URL to user's avatar image (default: "/assets/images/default-avatar.png")
  • className (optional): Additional CSS classes to apply

Note: To use these components in MDX files, make sure to import them at the top of your MDX file:

---
# Your frontmatter here
---

import Signup from '../components/Newsletter/Signup.astro';
import X from '../components/Tweet/index.astro';

# Your Content

<Signup title="Join Our Newsletter" />

<X id="1234567890123456789" author="example" content="Check out this amazing post!" />

Theme System

The project uses a modern CSS custom properties-based theme system that supports light and dark themes, with extensibility for additional themes like seasonal variations.

Architecture

The theme system consists of two main files that must be kept in sync:

  1. src/styles/themes.css - Pure CSS file containing all theme definitions using CSS custom properties
  2. src/lib/themes.ts - TypeScript registry defining available themes for the theme picker component

Adding a New Theme

To add a new theme (e.g., a holiday theme), follow these steps:

1. Add CSS Variables in src/styles/themes.css

Add a new CSS rule with your theme's custom properties:

/* Holiday Theme Example */
[data-theme="holiday"] {
  /* Background Colors */
  --color-bg: #0f172a;
  --color-bg-offset: #1e293b;

  /* Text Colors */
  --color-text: #f1f5f9;
  --color-text-offset: #cbd5e1;

  /* Primary Brand Colors */
  --color-primary: #dc2626;
  --color-primary-offset: #991b1b;
  --color-primary-bg: #7f1d1d;
  --color-primary-bg-hover: #991b1b;
  --color-primary-hover: #b91c1c;

  /* Secondary Colors */
  --color-secondary: #16a34a;
  --color-secondary-offset: #15803d;
  --color-secondary-bg: #052e16;

  /* Status Colors */
  --color-success: #16a34a;
  --color-success-offset: #22c55e;
  --color-success-bg: #052e16;

  --color-info: #0891b2;
  --color-info-bg: #164e63;

  --color-warning: #a16207;
  --color-warning-offset: #ca8a04;
  --color-warning-bg: #451a03;

  --color-danger: #dc2626;
  --color-danger-bg: #7f1d1d;

  /* Special Colors */
  --color-twitter: #1da1f2;
  --color-modal-background: #0f172a;

  /* Accent Colors */
  --color-accent: #fbbf24;
  --color-accent-bg: #451a03;

  /* Syntax Highlighting */
  --shiki-theme: 'github-dark';
}

2. Register Theme in src/lib/themes.ts

Add your theme to the themes array:

export const themes = [
  {
    id: 'default',
    name: 'Light',
    description: 'Clean light theme with blue accents',
    category: 'core'
  },
  {
    id: 'dark',
    name: 'Dark',
    description: 'Dark theme with navy background',
    category: 'core'
  },
  {
    id: 'holiday',
    name: 'Holiday',
    description: 'Festive red and green theme for the holidays',
    category: 'seasonal',
    seasonal: true
  }
];

3. Theme Properties

Each theme object supports these properties:

  • id (required): Unique identifier used in the data-theme attribute
  • name (required): Display name shown in the theme picker
  • description (optional): Tooltip description for the theme
  • category (optional): Used for grouping themes ('core', 'seasonal', etc.)
  • seasonal (optional): Boolean flag marking temporary/seasonal themes

CSS Variable Reference

The theme system provides these CSS custom properties:

Core Variables

  • --color-bg / --color-bg-offset - Background colors
  • --color-text / --color-text-offset - Text colors
  • --color-border - Border color

Brand Variables

  • --color-primary / --color-primary-offset - Primary brand colors
  • --color-secondary / --color-secondary-offset - Secondary colors

Status Variables

  • --color-success / --color-danger / --color-warning / --color-info - Status colors
  • Background variants available with -bg suffix

Special Variables

  • --color-accent - Purple accent color for highlights
  • --color-twitter - Twitter brand color
  • --color-modal-background - Modal overlay background
  • --shiki-theme - Syntax highlighting theme name

Theme Switching

Themes are applied by setting the data-theme attribute on the document element:

document.documentElement.setAttribute('data-theme', 'holiday');

The system also respects the user's system preference with @media (prefers-color-scheme: dark) for users who haven't explicitly chosen a theme.

Markdown Content Styling

The project uses a Rehype plugin (src/lib/markdown/rehype-tailwind-classes.ts) to automatically apply Tailwind CSS classes to rendered Markdown content. This replaces the previous SCSS-based content styling system.

Supported Markdown Elements

All standard Markdown elements are automatically styled with appropriate Tailwind classes:

  • Paragraphs: Proper spacing, readable font size, and line height
  • Headings: Typography hierarchy with serif fonts for h1-h3
  • Links: Underline effects with hover states
  • Images/Videos: Responsive sizing, centering, and shadows
  • Lists: Proper indentation and spacing
  • Code: Inline code highlighting and block code formatting
  • Tables: Full styling with borders and hover effects
  • Blockquotes: Left border accent with serif typography

Vendor Plugin Support

The Rehype plugin also handles specialized Markdown-it plugins:

Code Tabs (code-tabs)

For tabbed code blocks created by markdown-it plugins:

<!-- This would be processed by a markdown-it plugin -->
::: code-tabs
@tab JavaScript

```js
console.log('Hello, World!')

@tab TypeScript

const message: string = 'Hello, World!'
console.log(message)

:::

Auto-applied classes: Tab navigation, content panels, active states

Expandable Details (<details>)

For collapsible content sections:

<details>
<summary>Click to expand</summary>

This content will be collapsible with proper styling.

</details>

Auto-applied classes: Cursor pointer, remove default markers, content indentation

Named Code Blocks

For code blocks with filename labels:

```js filename="example.js"
const example = true
```

Auto-applied classes: Filename positioning, background styling, opacity effects

Share Highlight (<share-highlight>)

For text selection sharing functionality:

<share-highlight>
Select this text to see sharing options
</share-highlight>

Auto-applied classes: CSS custom properties for theming and interaction states

Migration from SCSS

This system replaces the previous _content.scss and vendor SCSS files:

  • src/styles/vendor/_codetab.scss → Integrated into Rehype plugin
  • src/styles/vendor/_expandable.scss → Integrated into Rehype plugin
  • src/styles/vendor/_namedCodeBlock.scss → Integrated into Rehype plugin
  • src/styles/vendor/_shareHighlight.scss → Integrated into Rehype plugin

The styling is now applied automatically to all rendered Markdown content without needing to import or reference any CSS classes.

CSS Fixes

  • Make sure a fixed height header doesn't cover title text on page navigation
h2:target {
  scroll-margin-top: var(--header-height);
}

:is(h2, h3, h4):target {
  scroll-margin-top: var(--header-height);
}

Also, we can play around with units like ex, or lh for dynamic spacing:

:target {
  scroll-margin-top: calc(var(--header-height) + 1lh);
}

This way, we get different offset based on the line-height of the target element which makes our spacing more dynamic.

  • Balance text in titles that span multiple lines
h2 {
  text-wrap: balance;
}
  • Match scroll bar color to page dark/light theme

Usually, websites switch between dark and light themes by assigning a class like dark or light to the body of the document. However, in our case, fixing the scrollbar requires applying the color-scheme property directly to the root element. Can we tackle this with CSS alone? Enter the :has() selector.

:root:has(body.dark) {
  color-scheme: dark;
}

See An Error

losst.pro has a modal that pops up for fixing mistakes:

Found a mistake in the text? Let me know about that. Highlight the text with the mistake and press Ctrl+Enter.

Astro Plugins

  • astro-auto-import
  • astro-navigation
  • astro-webfinger (Mastodon)

Sprites

Icons are managed through the astro-icon system with SVG files stored in src/icons/.

Adding a new icon:

  1. Add the SVG file to src/icons/ (use kebab-case naming)
  2. Update src/components/Sprite/sprites.ts to add the icon name to the SpriteName union type
  3. Use the icon with the Sprite component

Usage:

---
 import Sprite from 'components/Sprite.astro'
---
<Sprite name="fileName" class="customClassName"/>

See src/icons/README.md for detailed icon documentation.

Pages

Any .astro, .md, or .mdx file anywhere within the src/pages/ folder automatically became a page on your site.

Navigation

Astro uses standard HTML anchor elements to navigate between pages (also called routes), with traditional page refreshes.

<a href="/">Home</a>

Layouts

The <slot> element in an included layout will render any child elements between the <Layout> element used in pages

Get Markdown Frontmatter in a Layout

---
const { frontmatter } = Astro.props;
---
<h1>{frontmatter.title}</h1>
<p>Published on: {frontmatter.pubDate.slice(0,10)}</p>

Globbing

src/pages/blog.astro

---
import BaseLayout from '../layouts/BaseLayout.astro';
import BlogPost from '../components/BlogPost.astro';
const allPosts = Object.values(import.meta.glob('../pages/posts/*.md', { eager: true }));
const pageTitle = "My Astro Learning Blog"
---
<BaseLayout pageTitle={pageTitle}>
  <ul>
    {allPosts.map((post) => <BlogPost url={post.url} title={post.frontmatter.title} />)}
  </ul>
</BaseLayout>

src/components/BlogPost.astro

---
const { title, url } = Astro.props
---
<li><a href={url}>{title}</a></li>

Collections

Use getCollection instead of a glob. Frontmatter is returned on the data key.

---
import { getCollection } from "astro:content";

const allPosts = await getCollection("posts");
---
<BlogPost url={`/posts/${post.slug}/`} title={post.data.title} />

Tags

Each blog post should have a Frontmatter tag item:

tags: ["blogging"]

src/pages/tags/[tag].astro

---
import BaseLayout from '../../layouts/BaseLayout.astro';
import BlogPost from '../../components/BlogPost.astro';

export async function getStaticPaths() {
  const allPosts = Object.values(import.meta.glob('../posts/*.md', { eager: true }));

  const uniqueTags = [...new Set(allPosts.map((post) => post.frontmatter.tags).flat())];

  return uniqueTags.map((tag) => {
    const filteredPosts = allPosts.filter((post) => post.frontmatter.tags.includes(tag));
    return {
      params: { tag },
      props: { posts: filteredPosts },
    };
  });
}

const { tag } = Astro.params;
const { posts } = Astro.props;
---
<BaseLayout pageTitle={tag}>
  <p>Posts tagged with {tag}</p>
  <ul>
    {posts.map((post) => <BlogPost url={post.url} title={post.frontmatter.title}/>)}
  </ul>
</BaseLayout>

Get the currently focused element on the page in the console

document.activeElement

Testing HTML with html-validate in Braid UI

import '@testing-library/jest-dom';
import 'html-validate/jest';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { BraidTestProvider } from '../../../entries/test';
import { Button, IconSend } from '..';

describe('Button', () => {
  it('should render valid html structure', () => {
    expect(
      renderToStaticMarkup(
        <BraidTestProvider>
          <Button>Button</Button>
          <Button icon={<IconSend />}>Button</Button>
        </BraidTestProvider>,
      ),
    ).toHTMLValidate({
      extends: ['html-validate:recommended'],
    });
  });
});

Run a single test

clear && TS_NODE_PROJECT="tsconfig.jest.json" yarn jest eleventy/nunjucksAsyncShortcodes/asyncImageHandler/utils.spec.js --projects test/jest/jest.config.node.ts

Testing

The project uses a dual testing setup:

  • Unit Tests: Vitest for testing individual functions and components
  • E2E Tests: Playwright for end-to-end browser testing

Running Tests

Run all tests (unit + e2e):

npm test

Run only unit tests:

npm run test:unit

Run only e2e tests:

npm run test:e2e

Run tests with coverage report:

npm run test:coverage

Code Coverage

The project uses Vitest with the v8 coverage provider to generate comprehensive test coverage reports.

Viewing Coverage Reports:

After running npm run test:coverage, coverage reports are generated in multiple formats:

  1. Console Output - Summary displayed in terminal
  2. HTML Report - Interactive browsable report at ./coverage/index.html
  3. JSON Report - Machine-readable at ./coverage/coverage-final.json
  4. LCOV Report - Standard format for CI/CD tools at ./coverage/lcov.info

To view the HTML coverage report in your browser:

# After running coverage
open coverage/index.html        # macOS
xdg-open coverage/index.html    # Linux
start coverage/index.html       # Windows

Coverage Report Locations:

  • All coverage reports are stored in the ./coverage directory
  • The directory is automatically cleaned before each coverage run
  • Coverage includes: src/**/*.{ts,tsx,astro} and scripts/**/*.ts
  • Excludes: test files, type definitions, and test directories

Note: The coverage/ directory should be added to .gitignore to avoid committing generated reports.

Unit Test Structure

Unit tests are located alongside their source files:

  • src/**/*.spec.ts - Component and library tests
  • scripts/**/__tests__/*.spec.ts - Build script tests

Example test file locations:

  • src/lib/helpers/formatDate.spec.ts
  • scripts/build/__tests__/favicon.spec.ts

Snippet to search for string in project with exclude directories

clear && egrep -rnw './' --exclude-dir=node_modules --exclude-dir=.yarn --exclude-dir=yarn.lock --exclude-dir=public --exclude-dir=.cache -e 'searchString'

Clear Jest's cache

npx jest --clearCache

For install errors that report error installing sharp, recommended install. See jest.setup.jsdom.ts

npm install --platform=linux --arch=x64 sharp

Check if a CSS selector is valid in the browser console

document.querySelector('.contact___callout-flex')

Test the value of a CSS variable when using calc() in the browser console

document.getElementById('header__theme-icon').getBoundingClientRect().width

About

Webstack Builders Website in Astro

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •