feat: add basic image component using the picture of Astro

This commit is contained in:
Stefan Imhoff
2026-01-24 15:36:55 +01:00
committed by Stefan Imhoff
parent a05dec6363
commit c5c1b15013
2 changed files with 139 additions and 16 deletions

View File

@@ -0,0 +1,120 @@
---
import { Picture } from 'astro:assets';
import type { ImageMetadata } from 'astro';
import TextLink from './TextLink.astro';
interface Props {
alt?: string;
caption?: string;
class?: string;
decoding?: 'async' | 'sync' | 'auto';
height?: string | number;
loading?: 'lazy' | 'eager';
noSpacing?: boolean;
quality?: number;
size?: 'regular' | 'wide' | 'fullsize';
source?: string;
sourceUrl?: string;
src: string;
width?: string | number;
}
const {
alt,
caption,
class: className,
decoding = 'async',
height,
loading = 'lazy',
noSpacing,
quality,
size,
source,
sourceUrl,
src,
width,
...props
} = Astro.props;
// Normalize dimensions
const parseDim = (dim: string | number | undefined) => (dim ? Number(dim) : undefined);
const parsedWidth = parseDim(width);
const parsedHeight = parseDim(height);
// Resolve Image Source
let localImage: ImageMetadata | undefined;
let remoteImage: string = src;
// Get all the image metadata
const images = import.meta.glob<{ default: ImageMetadata }>('/src/images/**/*');
if (src.startsWith('/images')) {
const key = '/src' + src;
const loader = images[key as keyof typeof images];
if (typeof loader === 'function') {
const mod = await loader();
localImage = mod.default;
}
}
// Define sizes attribute for responsive images
const sizes = '(max-width: 440px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 85vw, 1100px';
---
<figure
class:list={[
'mbs-0 mie-0 mis-0',
{
'figure-wide': size === 'wide',
'figure-fullsize': size === 'fullsize',
'mbe-13': true,
},
className,
]}
{...props}
>
<div class="figure-content flex flex-wrap gap-6 md:flex-nowrap [&_div]:flex-grow">
{
localImage ? (
<Picture
alt={alt ?? ''}
class="rounded-2"
decoding={decoding}
formats={['avif', 'webp', 'jpg', 'png']}
loading={loading}
quality={quality}
sizes={sizes}
src={localImage}
width={parsedWidth}
height={parsedHeight}
/>
) : (
<img
alt={alt ?? ''}
class="rounded-2"
loading={loading}
src={remoteImage}
width={parsedWidth}
height={parsedHeight}
/>
)
}
</div>
{
(caption || source) && (
<figcaption class="text-center text-2 mbs-2 [text-wrap:balance]">
{caption}
{caption && source ? '' : ''}
{source &&
(sourceUrl ? (
<cite>
<TextLink href={sourceUrl}>{source}</TextLink>
</cite>
) : (
<cite>{source}</cite>
))}
</figcaption>
)
}
</figure>

View File

@@ -3,6 +3,7 @@ import { YouTube } from '@astro-community/astro-embed-youtube';
import AmazonBook from './components/AmazonBook.astro';
import AppleTVFlag from './components/AppleTVFlag.astro';
import Banner from './components/Banner.astro';
import BasicImage from './components/BasicImage.astro';
import Blockquote from './components/Blockquote.astro';
import Book from './components/Book.astro';
import Bookshelf from './components/Bookshelf.astro';
@@ -38,13 +39,11 @@ import UnorderedList from './components/UnorderedList.astro';
import Verse from './components/Verse.astro';
export const mapping = {
a: TextLink,
ProductLink,
AmazonBook,
AppleTVFlag,
Banner,
BasicImage,
Blockquote,
blockquote: Blockquote,
Book,
Bookshelf,
ColorStack,
@@ -54,6 +53,22 @@ export const mapping = {
EmailLink,
Figure,
Flag,
Image,
MarkdownImage,
MoreLink,
NetflixFlag,
OdyseeVideo,
PrimeVideoFlag,
ProductLink,
ProjectIntro,
Pullquote,
Ruby,
Spotify,
ThemeBox,
Verse,
YouTube,
a: TextLink,
blockquote: Blockquote,
h1: Title,
h2: Headline,
h3: Subheadline,
@@ -61,24 +76,11 @@ export const mapping = {
h5: Subsubheadline,
h6: Subsubheadline,
hr: Divider,
Image,
img: MarkdownImage,
li: ListItem,
MarkdownImage,
MoreLink,
NetflixFlag,
OdyseeVideo,
ol: OrderedList,
p: Text,
PrimeVideoFlag,
ProjectIntro,
Pullquote,
Ruby,
Spotify,
ThemeBox,
ul: UnorderedList,
Verse,
YouTube,
};
// Mapping for RSS feed to reduce the size of the feed
@@ -86,6 +88,7 @@ export const rssMapping = {
AmazonBook,
AppleTVFlag,
Banner,
BasicImage,
Blockquote,
Book,
Bookshelf,