more of the article layout + layout refactor

This commit is contained in:
Tony Sullivan 2023-09-13 09:36:44 -05:00
parent 635fb1510c
commit ae23274489
8 changed files with 386 additions and 291 deletions

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,131 @@
---
import type { HTMLAttributes } from 'astro/types';
import { Image } from 'astro:assets';
import { CollectionEntry } from 'astro:content';
import getReadingTime from 'reading-time';
import FormattedDate from './FormattedDate.astro';
import Icon from './Icon.astro';
import TagList from './TagList.astro';
import settings from '../settings.js';
interface Props extends HTMLAttributes<'header'> {
article: CollectionEntry<'posts'>;
}
const { article, ...attrs } = Astro.props;
const { text: readingTime } = await getReadingTime(article.body);
---
<header {...attrs}>
<a href="/" class="back">
<Icon icon="arrow-left" color="var(--theme-accent-dark)" size="1.25rem" />
<span>Back to feed</span>
</a>
<div class="cover">
{
article.data.cover && (
<Image src={article.data.cover.src} alt={article.data.cover.alt} class="cover" />
)
}
<div class="author">
<Image src={settings.avatar.src} alt={settings.avatar.alt} width={80} />
<p class="u-name">{settings.name}</p>
<p class="u-nickname">{settings.username}</p>
</div>
</div>
<div class="meta">
<FormattedDate date={article.data.pubDate} />
<span>•</span>
<p>
<Icon icon="clock" size="1rem" />
<span>{readingTime}</span>
</p>
</div>
<h1>{article.data.title}</h1>
{article.data.tags?.length > 0 && <TagList tags={article.data.tags} />}
</header>
<style>
header {
display: flex;
flex-direction: column;
gap: 1rem;
}
.back {
text-decoration: none;
font-weight: bold;
}
.back {
display: flex;
align-items: center;
gap: 0.5rem;
margin-inline-start: -4px;
}
.cover {
position: relative;
width: 100%;
min-height: 150px;
background: var(--theme-gradient-main);
border-radius: 1.25rem;
margin-bottom: 1.25rem;
}
.cover > img {
width: 100%;
height: 100%;
aspect-ratio: 2.777;
object-fit: cover;
}
.author {
position: absolute;
display: flex;
align-items: center;
gap: 0.5rem;
bottom: 0%;
right: 3.125rem;
translate: 0 50%;
box-shadow: var(--theme-shadow-md);
background-color: var(--theme-accent-light);
padding: 0.75rem 1.5rem;
border-radius: 1.25rem;
border: 3px solid var(--theme-accent-dark);
}
.author img {
width: 2.5rem;
height: 2.5rem;
}
.author .u-name {
font-family: var(--font-brand);
font-weight: bold;
}
.author .u-nickname {
color: var(--theme-gray-300);
font-size: var(--theme-text-sm);
font-weight: 600;
}
.meta {
display: flex;
align-items: center;
gap: 1rem;
font-size: var(--theme-text-sm);
color: var(--theme-gray-200);
}
.meta svg {
margin-block-end: 0.25rem;
}
</style>

View file

@ -4,28 +4,26 @@ import ThemeToggle from './ThemeToggle.astro';
---
<header>
<a class="site-name" href="/">{settings.username}</a>
<ThemeToggle />
<a class="site-name" href="/">{settings.username}</a>
<ThemeToggle />
</header>
<style>
header {
display: flex;
justify-content: space-between;
align-items: center;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
}
.site-name {
font-size: var(--theme-text-lg);
font-weight: 700;
font-family: var(--theme-font-brand);
text-decoration: none;
color: transparent;
background: var(--theme-gradient-main);
background-clip: text;
}
.site-name {
font-size: var(--theme-text-lg);
font-weight: 700;
font-family: var(--theme-font-brand);
text-decoration: none;
color: var(--theme-accent-dark);
}
.site-name:hover {
text-decoration: 1px solid underline var(--theme-accent-dark);
}
.site-name:hover {
text-decoration: 1px solid underline var(--theme-accent-dark);
}
</style>

View file

@ -0,0 +1,33 @@
---
import type { HTMLAttributes } from 'astro/types';
export interface Props extends HTMLAttributes<'article'> {}
---
<article {...Astro.props}>
<slot />
</article>
<style>
article > :global(* + *) {
margin-block-start: 1em;
}
article > :global(p:first-child::first-letter) {
float: left;
font-size: 3.5rem;
padding-inline-end: 0.5rem;
padding-block-start: 0.35rem;
font-weight: bold;
}
article :global(img, video, figure) {
margin-inline: auto;
}
@media (min-width: 50em) {
article > :global(* + *) {
margin-block-start: 1.5em;
}
}
</style>

View file

@ -1,4 +1,5 @@
---
import { Image } from 'astro:assets';
import settings from '../settings';
import Icon from './Icon.astro';
@ -8,155 +9,146 @@ const socialLinks = Object.entries(settings.social) as SocialEntry[];
---
<div class="profile">
<div>
<div class="avatar">
<img width="110" height="110" {...settings.avatar} />
</div>
<h1>
{settings.name}
<small>{settings.username}</small>
</h1>
</div>
<div class="bio-sections">
<div class="bio">
<p>🚀 <a href="https://astro.build/">Astro</a> Mascot</p>
<p>😊 The cutest</p>
<p>🎨 Whimsical Speedy Web</p>
</div>
<div class="bio">
{
settings.pronouns && (
<p>
<Icon icon="user" color="var(--theme-accent-dark)" size="1.75rem" />
<span class="sr-only">Pronouns</span>
{settings.pronouns}
</p>
)
}
{
settings.location && (
<p>
<Icon
icon="location-point"
color="var(--theme-accent-dark)"
size="1.75rem"
/>
<span class="sr-only">Location</span>
{settings.location}
</p>
)
}
{
settings.homepage && (
<p>
<Icon
icon="link-h"
color="var(--theme-accent-dark)"
size="1.75rem"
/>
<span class="sr-only">Homepage</span>
<a href={settings.homepage}>
{settings.homepage.replace(/^https?:\/\/(www\.)?/, '')}
</a>
</p>
)
}
<p>
<Icon icon="rss-alt" color="var(--theme-accent-dark)" size="1.75rem" />
<a href="/rss.xml">RSS Feed</a>
</p>
</div>
<ul class="social">
{
socialLinks.map(([key, url]) => (
<li>
<a href={url}>
<Icon icon={`${key}-logo`} size="1.75rem" />
</a>
</li>
))
}
</ul>
</div>
<div>
<div class="avatar">
<Image {...settings.avatar} width={220} />
</div>
<h1>
{settings.name}
<small>{settings.username}</small>
</h1>
</div>
<div class="bio-sections">
<div class="bio">
<p>🚀 <a href="https://astro.build/">Astro</a> Mascot</p>
<p>😊 The cutest</p>
<p>🎨 Whimsical Speedy Web</p>
</div>
<div class="bio">
{
settings.pronouns && (
<p>
<Icon icon="user" color="var(--theme-accent-dark)" size="1.75rem" />
<span class="sr-only">Pronouns</span>
{settings.pronouns}
</p>
)
}
{
settings.location && (
<p>
<Icon icon="location-point" color="var(--theme-accent-dark)" size="1.75rem" />
<span class="sr-only">Location</span>
{settings.location}
</p>
)
}
{
settings.homepage && (
<p>
<Icon icon="link-h" color="var(--theme-accent-dark)" size="1.75rem" />
<span class="sr-only">Homepage</span>
<a href={settings.homepage}>{settings.homepage.replace(/^https?:\/\/(www\.)?/, '')}</a>
</p>
)
}
<p>
<Icon icon="rss-alt" color="var(--theme-accent-dark)" size="1.75rem" />
<a href="/rss.xml">RSS Feed</a>
</p>
</div>
<ul class="social">
{
socialLinks.map(([key, url]) => (
<li>
<a href={url}>
<Icon icon={`${key}-logo`} size="1.75rem" />
</a>
</li>
))
}
</ul>
</div>
</div>
<style>
.profile {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.profile {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.avatar {
display: inline-block;
position: relative;
}
.avatar {
display: inline-block;
position: relative;
}
.avatar::after {
border-radius: var(--theme-radius-full);
position: absolute;
content: '';
inset: 0;
border: 0.1875rem solid var(--theme-text);
}
.avatar::after {
border-radius: var(--theme-radius-full);
position: absolute;
content: '';
inset: 0;
border: 0.1875rem solid var(--theme-text);
}
.avatar img {
height: auto;
}
.avatar img {
width: 110px;
height: 110px;
}
h1 {
display: flex;
flex-direction: column;
font-size: var(--theme-text-xl);
}
h1 {
display: flex;
flex-direction: column;
font-size: var(--theme-text-xl);
}
small {
font-size: var(--theme-text-base);
font-family: var(--theme-font-body);
color: var(--theme-gray-200);
}
small {
font-size: var(--theme-text-base);
font-family: var(--theme-font-body);
color: var(--theme-gray-200);
}
.bio-sections {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: flex-start;
gap: 1rem 1.75rem;
}
.bio-sections {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: flex-start;
gap: 1rem 1.75rem;
}
.bio-sections > :nth-child(2) {
grid-row: span 2;
}
.bio-sections > :nth-child(2) {
grid-row: span 2;
}
.bio {
font-size: var(--theme-text-sm);
font-weight: 500;
}
.bio {
font-size: var(--theme-text-sm);
font-weight: 500;
}
.bio > * + * {
margin-top: 0.75rem;
}
.bio > * + * {
margin-top: 0.75rem;
}
.bio :global(svg) {
/* Slightly hacky way to avoid the icon height being included in the box calculation. */
margin: -50% 0;
}
.bio :global(svg) {
/* Slightly hacky way to avoid the icon height being included in the box calculation. */
margin: -50% 0;
}
.social {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
list-style: none;
padding: 0;
}
.social {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
list-style: none;
padding: 0;
}
.social a {
color: var(--theme-accent-dark);
}
.social a {
color: var(--theme-accent-dark);
}
@media (min-width: 50em) {
.bio-sections {
display: flex;
flex-direction: column;
}
}
@media (min-width: 50em) {
.bio-sections {
display: flex;
flex-direction: column;
}
}
</style>

View file

@ -1,11 +1,9 @@
---
import { Image } from 'astro:assets';
import type { CollectionEntry } from 'astro:content';
import getReadingTime from 'reading-time';
import Layout, { type Props as LayoutProps } from './Base.astro';
import FormattedDate from '../components/FormattedDate.astro';
import TagList from '../components/TagList.astro';
import ArticleHeader from '../components/ArticleHeader.astro';
import Icon from '../components/Icon.astro';
import Prose from '../components/Prose.astro';
export interface Props extends LayoutProps {
article: CollectionEntry<'posts'>;
@ -16,118 +14,56 @@ export interface Props extends LayoutProps {
const { article, next, prev } = Astro.props;
const { Content, headings } = await article.render();
const readingTime = getReadingTime(article.body).text;
---
<Layout ...Astro.props} wrapperReverse>
<header>
<a href="/" class="back">
<Icon icon="arrow-left" color="var(--theme-accent-dark)" size="1.25rem" />
<span>Back to feed</span>
</a>
{article.data.cover && <Image src={article.data.cover.src} alt={article.data.cover.alt} class="cover" />}
<div class="meta">
<FormattedDate date={article.data.pubDate} />
<span>•</span>
<p>
<Icon icon="clock" size="1rem" />
<span>{readingTime}</span>
</p>
</div>
<h1>{article.data.title}</h1>
{article.data.tags?.length > 0 && (
<TagList tags={article.data.tags} />
)}
</header>
<div class="content">
<ArticleHeader {article} class="header" />
<Prose class="content">
<Content />
</div>
{headings.length > 0 && (
<aside>
<h3>Table of contents</h3>
<ul>
{headings.map(({ slug, text }) => (
<li>
<a href={`${Astro.url.pathname}#${slug}`}>{text}</a>
</li>
))}
</ul>
</aside>
)}
{(next || prev) && (
<footer>
{prev && <a href={`/post/${prev.slug}`} class="prev">
<Icon icon="arrow-left" size="1.25rem" color="var(--theme-accent-dark)" />
<span>Previous: {prev.data.title}</span>
</a>}
{next && <a href={`/post/${next.slug}`} class="next">
<span>Next: {next.data.title}</span>
<Icon icon="arrow-right" size="1.25rem" color="var(--theme-accent-dark)" />
</a>}
</footer>
)}
</Prose>
{
headings.length > 0 && (
<aside>
<h3>Table of contents</h3>
<ul>
{headings.map(({ slug, text }) => (
<li>
<a href={`${Astro.url.pathname}#${slug}`}>{text}</a>
</li>
))}
</ul>
</aside>
)
}
{
(next || prev) && (
<footer>
{prev && (
<a href={`/post/${prev.slug}`} class="prev">
<Icon icon="arrow-left" size="1.25rem" color="var(--theme-accent-dark)" />
<span>Previous: {prev.data.title}</span>
</a>
)}
{next && (
<a href={`/post/${next.slug}`} class="next">
<span>Next: {next.data.title}</span>
<Icon icon="arrow-right" size="1.25rem" color="var(--theme-accent-dark)" />
</a>
)}
</footer>
)
}
</Layout>
<style>
header, footer {
.header,
footer {
grid-column: 1 / -1;
}
header {
display: flex;
flex-direction: column;
gap: 1rem;
}
.content > :global(* + *) {
margin-block-start: 1em;
}
.content > :global(p:first-child::first-letter) {
float: left;
font-size: 3.5rem;
padding-inline-end: .5rem;
padding-block-start: .35rem;
font-weight: bold;
}
.content :global(img, video, figure) {
margin-inline: auto;
}
.meta {
display: flex;
align-items: center;
gap: 1rem;
font-size: var(--theme-text-sm);
color: var(--theme-gray-200);
}
.meta svg {
margin-block-end: 0.25rem;
}
.back {
text-decoration: none;
font-weight: bold;
}
.back {
display: flex;
align-items: center;
gap: 0.5rem;
margin-inline-start: -4px;
}
.cover {
height: auto;
aspect-ratio: 2.777;
object-fit: cover;
border-radius: 1.25rem;
margin-block-start: 0.5rem;
}
footer {
display: flex;
justify-content: space-between;
@ -165,9 +101,5 @@ const readingTime = getReadingTime(article.body).text;
aside {
display: block;
}
.content > :global(* + *) {
margin-block-start: 1.5em;
}
}
</style>

View file

@ -1,5 +1,6 @@
---
import type { GetStaticPathsOptions, Page } from 'astro';
import { Image } from 'astro:assets';
import type { CollectionEntry } from 'astro:content';
import Pagination from '../components/Pagination.astro';
import TagList from '../components/TagList.astro';
@ -28,11 +29,11 @@ const { page } = Astro.props;
<article class="card">
<header>
<div style="display: flex;gap:0.875rem;align-items:center;">
<img
style="border-radius: var(--theme-radius-full);background-color:var(--theme-shade-subtle);"
width="60"
height="60"
{...settings.avatar}
<Image
src={settings.avatar.src}
width={120}
class="u-avatar"
alt={settings.avatar.alt}
/>
<div>
<p style="font-family:var(--theme-font-brand);font-weight:700;font-size:var(--theme-text-lg)">
@ -89,4 +90,10 @@ const { page } = Astro.props;
header > * + * {
margin-top: 0.625rem;
}
.u-avatar {
width: 3.75rem;
height: 3.75rem;
border-radius: var(--theme-radius-full);
background-color: var(--theme-shade-subtle);
}
</style>

View file

@ -1,24 +1,26 @@
import avatar from './assets/avatar.webp'
export default {
name: 'Houston Astro',
username: '@houston',
avatar: {
src: '/avatar.webp',
alt: 'Astro mascot Houston smiling',
},
rss: {
title: 'Houston Astros Feed',
description: 'Stay up-to-date with the latest posts from Houston Astro!',
},
pronouns: 'They/Them',
location: 'Space',
homepage: 'https://astro.build',
social: {
twitter: 'https://twitter.com/astrodotbuild',
twitch: 'https://www.twitch.tv/bholmesdev',
github: 'https://github.com/withastro',
devto: 'https://dev.to/search?q=astro',
codepen: 'https://codepen.io/delucis',
mastodon: 'https://m.webtoo.ls/@astro',
youtube: 'https://www.youtube.com/@astrodotbuild',
},
name: 'Houston Astro',
username: '@houston',
avatar: {
src: avatar,
alt: 'Astro mascot Houston smiling',
},
rss: {
title: 'Houston Astros Feed',
description: 'Stay up-to-date with the latest posts from Houston Astro!',
},
pronouns: 'They/Them',
location: 'Space',
homepage: 'https://astro.build',
social: {
twitter: 'https://twitter.com/astrodotbuild',
twitch: 'https://www.twitch.tv/bholmesdev',
github: 'https://github.com/withastro',
devto: 'https://dev.to/search?q=astro',
codepen: 'https://codepen.io/delucis',
mastodon: 'https://m.webtoo.ls/@astro',
youtube: 'https://www.youtube.com/@astrodotbuild',
},
} as const;