more of the article layout + layout refactor
This commit is contained in:
parent
635fb1510c
commit
ae23274489
8 changed files with 386 additions and 291 deletions
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
131
examples/social-feed/src/components/ArticleHeader.astro
Normal file
131
examples/social-feed/src/components/ArticleHeader.astro
Normal 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>
|
|
@ -20,9 +20,7 @@ import ThemeToggle from './ThemeToggle.astro';
|
|||
font-weight: 700;
|
||||
font-family: var(--theme-font-brand);
|
||||
text-decoration: none;
|
||||
color: transparent;
|
||||
background: var(--theme-gradient-main);
|
||||
background-clip: text;
|
||||
color: var(--theme-accent-dark);
|
||||
}
|
||||
|
||||
.site-name:hover {
|
||||
|
|
33
examples/social-feed/src/components/Prose.astro
Normal file
33
examples/social-feed/src/components/Prose.astro
Normal 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>
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
import { Image } from 'astro:assets';
|
||||
import settings from '../settings';
|
||||
import Icon from './Icon.astro';
|
||||
|
||||
|
@ -10,7 +11,7 @@ const socialLinks = Object.entries(settings.social) as SocialEntry[];
|
|||
<div class="profile">
|
||||
<div>
|
||||
<div class="avatar">
|
||||
<img width="110" height="110" {...settings.avatar} />
|
||||
<Image {...settings.avatar} width={220} />
|
||||
</div>
|
||||
<h1>
|
||||
{settings.name}
|
||||
|
@ -36,11 +37,7 @@ const socialLinks = Object.entries(settings.social) as SocialEntry[];
|
|||
{
|
||||
settings.location && (
|
||||
<p>
|
||||
<Icon
|
||||
icon="location-point"
|
||||
color="var(--theme-accent-dark)"
|
||||
size="1.75rem"
|
||||
/>
|
||||
<Icon icon="location-point" color="var(--theme-accent-dark)" size="1.75rem" />
|
||||
<span class="sr-only">Location</span>
|
||||
{settings.location}
|
||||
</p>
|
||||
|
@ -49,15 +46,9 @@ const socialLinks = Object.entries(settings.social) as SocialEntry[];
|
|||
{
|
||||
settings.homepage && (
|
||||
<p>
|
||||
<Icon
|
||||
icon="link-h"
|
||||
color="var(--theme-accent-dark)"
|
||||
size="1.75rem"
|
||||
/>
|
||||
<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>
|
||||
<a href={settings.homepage}>{settings.homepage.replace(/^https?:\/\/(www\.)?/, '')}</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
@ -101,7 +92,8 @@ const socialLinks = Object.entries(settings.social) as SocialEntry[];
|
|||
}
|
||||
|
||||
.avatar img {
|
||||
height: auto;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
|
@ -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,34 +14,17 @@ 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 && (
|
||||
</Prose>
|
||||
|
||||
{
|
||||
headings.length > 0 && (
|
||||
<aside>
|
||||
<h3>Table of contents</h3>
|
||||
<ul>
|
||||
|
@ -54,80 +35,35 @@ const readingTime = getReadingTime(article.body).text;
|
|||
))}
|
||||
</ul>
|
||||
</aside>
|
||||
)}
|
||||
{(next || prev) && (
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
(next || prev) && (
|
||||
<footer>
|
||||
{prev && <a href={`/post/${prev.slug}`} class="prev">
|
||||
{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">
|
||||
</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>
|
||||
</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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import avatar from './assets/avatar.webp'
|
||||
|
||||
export default {
|
||||
name: 'Houston Astro',
|
||||
username: '@houston',
|
||||
avatar: {
|
||||
src: '/avatar.webp',
|
||||
src: avatar,
|
||||
alt: 'Astro mascot Houston smiling',
|
||||
},
|
||||
rss: {
|
||||
|
|
Loading…
Reference in a new issue