Adding storybook and stories while also fixing accessibility in the external link component.

This commit is contained in:
Bradley Shellnut 2025-08-21 17:15:04 -07:00
parent d18f6b0a9f
commit fc4ee0c9ae
20 changed files with 1173 additions and 76 deletions

19
.storybook/main.ts Normal file
View file

@ -0,0 +1,19 @@
import type { StorybookConfig } from '@storybook/sveltekit';
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|ts|svelte)"],
addons: [
"@storybook/addon-svelte-csf",
"@chromatic-com/storybook",
"@storybook/addon-docs",
"@storybook/addon-a11y"
],
framework: {
name: "@storybook/sveltekit",
options: {},
},
core: {
disableTelemetry: true,
},
};
export default config;

16
.storybook/preview.ts Normal file
View file

@ -0,0 +1,16 @@
import '../src/styles/styles.pcss';
import type { Preview } from '@storybook/sveltekit';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View file

@ -1,61 +1,68 @@
{ {
"name": "personal-website-sveltekit", "name": "personal-website-sveltekit",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host", "dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "npm run test:integration && npm run test:unit", "test": "npm run test:integration && npm run test:unit",
"test:ui": "svelte-kit sync && playwright test --ui", "test:ui": "svelte-kit sync && playwright test --ui",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "biome lint --error-on-warnings .", "lint": "biome lint --error-on-warnings .",
"format": "biome format --write .", "format": "biome format --write .",
"test:integration": "playwright test", "storybook": "storybook dev -p 6006",
"test:unit": "vitest" "test:integration": "playwright test",
}, "test:unit": "vitest"
"devDependencies": { },
"@biomejs/biome": "^2.1.4", "devDependencies": {
"@internationalized/date": "^3.8.2", "@biomejs/biome": "^2.1.4",
"@playwright/test": "^1.54.2", "@chromatic-com/storybook": "^4.1.1",
"@sveltejs/enhanced-img": "^0.5.1", "@internationalized/date": "^3.8.2",
"@sveltejs/kit": "^2.29.0", "@playwright/test": "^1.54.2",
"@sveltejs/vite-plugin-svelte": "^5.1.1", "@storybook/addon-a11y": "^9.1.3",
"@unpic/svelte": "^1.0.0", "@storybook/addon-docs": "^9.1.3",
"@zerodevx/svelte-img": "^2.1.2", "@storybook/addon-svelte-csf": "^5.0.8",
"autoprefixer": "^10.4.21", "@storybook/sveltekit": "^9.1.3",
"just-intersect": "^4.3.0", "@sveltejs/enhanced-img": "^0.5.1",
"postcss": "^8.5.6", "@sveltejs/kit": "^2.29.0",
"postcss-custom-media": "^11.0.6", "@sveltejs/vite-plugin-svelte": "^5.1.1",
"postcss-import": "^16.1.1", "@unpic/svelte": "^1.0.0",
"postcss-load-config": "^6.0.1", "@zerodevx/svelte-img": "^2.1.2",
"postcss-preset-env": "^10.2.4", "autoprefixer": "^10.4.21",
"satori": "^0.12.2", "just-intersect": "^4.3.0",
"satori-html": "^0.3.2", "postcss": "^8.5.6",
"svelte": "^5.38.1", "postcss-custom-media": "^11.0.6",
"svelte-check": "^4.3.1", "postcss-import": "^16.1.1",
"svelte-meta-tags": "^4.4.0", "postcss-load-config": "^6.0.1",
"svelte-preprocess": "^6.0.3", "postcss-preset-env": "^10.2.4",
"svelte-sequential-preprocessor": "^2.0.2", "satori": "^0.12.2",
"tslib": "^2.8.1", "satori-html": "^0.3.2",
"typescript": "^5.9.2", "storybook": "^9.1.3",
"vanilla-lazyload": "^19.1.3", "svelte": "^5.38.1",
"vite": "^6.3.5", "svelte-check": "^4.3.1",
"vite-imagetools": "^7.1.1", "svelte-meta-tags": "^4.4.0",
"vitest": "^3.2.4" "svelte-preprocess": "^6.0.3",
}, "svelte-sequential-preprocessor": "^2.0.2",
"dependencies": { "tslib": "^2.8.1",
"@resvg/resvg-js": "^2.6.2", "typescript": "^5.9.2",
"@sveltejs/adapter-node": "^5.2.14", "vanilla-lazyload": "^19.1.3",
"@vercel/og": "^0.6.8", "vite": "^6.3.5",
"bits-ui": "2.9.2", "vite-imagetools": "^7.1.1",
"flexsearch": "^0.8.205", "vitest": "^3.2.4"
"ioredis": "^5.7.0", },
"lucide-svelte": "^0.539.0", "dependencies": {
"scrape-it": "^6.1.11", "@resvg/resvg-js": "^2.6.2",
"sharp": "^0.34.3", "@sveltejs/adapter-node": "^5.2.14",
"svelte-local-storage-store": "^0.6.4" "@vercel/og": "^0.6.8",
} "bits-ui": "2.9.2",
"flexsearch": "^0.8.205",
"ioredis": "^5.7.0",
"lucide-svelte": "^0.539.0",
"scrape-it": "^6.1.11",
"sharp": "^0.34.3",
"svelte-local-storage-store": "^0.6.4"
}
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Articles from "./Articles.svelte";
const { Story } = defineMeta({
title: "Components/Articles",
component: Articles,
tags: ["autodocs"],
args: {
data: { articles: [], totalArticles: 0, classes: [], compact: false },
},
argTypes: {
data: { control: "object" },
},
});
</script>
<Story name="Default" args={{ data: { articles: [], totalArticles: 0, classes: [], compact: false } }} />

View file

@ -0,0 +1,13 @@
<script context="module" lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ArticlesSkeleton from "./ArticlesSkeleton.svelte";
const { Story } = defineMeta({
title: "Components/ArticlesSkeleton",
component: ArticlesSkeleton,
tags: ["autodocs"],
args: { count: 6, classes: [] },
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,38 @@
<script context="module" lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import Bandcamp from "./Bandcamp.svelte";
const sampleAlbums = [
{
title: "Album One",
artist: "Artist A",
url: "https://example.com",
artwork: "https://picsum.photos/230?1",
src: undefined,
},
{
title: "Album Two",
artist: "Artist B",
url: "https://example.com",
artwork: "https://picsum.photos/230?2",
src: undefined,
},
{
title: "Album Three",
artist: "Artist C",
url: "https://example.com",
artwork: "https://picsum.photos/230?3",
src: undefined,
},
];
const { Story } = defineMeta({
title: "Components/Bandcamp",
component: Bandcamp,
tags: ["autodocs"],
args: { albums: sampleAlbums },
argTypes: { albums: { control: "object" } },
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,40 @@
<script context="module" lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ContactHub from "./ContactHub.svelte";
const defaultUserNames = {
x: "example_x",
blueSky: "example.bsky.social",
linkedIn: "example-linkedin",
github: "example-github",
email: "user@example.com",
};
const { Story } = defineMeta({
title: "Components/ContactHub",
component: ContactHub,
tags: ["autodocs"],
args: {
showBlueSky: true,
showEmail: true,
showGithub: true,
showLinkedIn: true,
showX: true,
userNames: defaultUserNames,
showText: true,
justify: true,
},
argTypes: {
showBlueSky: { control: "boolean" },
showEmail: { control: "boolean" },
showGithub: { control: "boolean" },
showLinkedIn: { control: "boolean" },
showX: { control: "boolean" },
showText: { control: "boolean" },
justify: { control: "boolean" },
userNames: { control: "object" },
},
});
</script>
<Story name="Default" />

View file

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { Mail } from "lucide-svelte"; import { Mail } from "lucide-svelte";
import ExternalLink from "$lib/components/ExternalLink.svelte";
import { import {
blueSkyIcon, blueSkyIcon,
gitHubIcon, gitHubIcon,
linkedInIcon, linkedInIcon,
xIcon, xIcon,
} from "../util/logoIcons.svelte"; } from "../util/logoIcons.svelte";
import ExternalLink from '$lib/components/ExternalLink.svelte';
interface Props { interface Props {
showBlueSky?: boolean; showBlueSky?: boolean;
@ -37,36 +37,66 @@
<div class:justifyCenter={justify}> <div class:justifyCenter={justify}>
{#if showX && userNames?.x} {#if showX && userNames?.x}
<ExternalLink <ExternalLink
linkData={{ href: `https://www.x.com/${userNames.x}`, ariaLabel: 'Contact through X', title: 'Contact through X', target: '_blank', clazz: "hub-icon x-contact" }} linkData={{
iconData={{ type: 'svg', icon: xIcon, iconClass: 'center' }} href: `https://www.x.com/${userNames.x}`,
ariaLabel: "Contact through X",
title: "Contact through X",
target: "_blank",
clazz: "hub-icon x-contact",
}}
iconData={{ type: "svg", icon: xIcon, iconClass: "center" }}
textData={{ showIcon: true }} textData={{ showIcon: true }}
/> />
{/if} {/if}
{#if showBlueSky && userNames?.blueSky} {#if showBlueSky && userNames?.blueSky}
<ExternalLink <ExternalLink
linkData={{ href: `https://bsky.app/profile/${userNames.blueSky}`, ariaLabel: 'Contact through Bluesky', title: 'Contact through Bluesky', target: '_blank', clazz: "hub-icon bluesky-contact" }} linkData={{
iconData={{ type: 'svg', icon: blueSkyIcon, iconClass: 'center' }} href: `https://bsky.app/profile/${userNames.blueSky}`,
ariaLabel: "Contact through Bluesky",
title: "Contact through Bluesky",
target: "_blank",
clazz: "hub-icon bluesky-contact",
}}
iconData={{ type: "svg", icon: blueSkyIcon, iconClass: "center" }}
textData={{ showIcon: true }} textData={{ showIcon: true }}
/> />
{/if} {/if}
{#if showLinkedIn && userNames?.linkedIn} {#if showLinkedIn && userNames?.linkedIn}
<ExternalLink <ExternalLink
linkData={{ href: `https://www.linkedin.com/in/${userNames.linkedIn}`, ariaLabel: 'Contact through LinkedIn', title: 'Contact through LinkedIn', target: '_blank', clazz: "hub-icon linkedIn-contact" }} linkData={{
iconData={{ type: 'svg', icon: linkedInIcon, iconClass: 'center' }} href: `https://www.linkedin.com/in/${userNames.linkedIn}`,
ariaLabel: "Contact through LinkedIn",
title: "Contact through LinkedIn",
target: "_blank",
clazz: "hub-icon linkedIn-contact",
}}
iconData={{ type: "svg", icon: linkedInIcon, iconClass: "center" }}
textData={{ showIcon: true }} textData={{ showIcon: true }}
/> />
{/if} {/if}
{#if showGithub && userNames?.github} {#if showGithub && userNames?.github}
<ExternalLink <ExternalLink
linkData={{ href: `https://www.github.com/${userNames.github}`, ariaLabel: 'Contact through Github', title: 'Contact through Github', target: '_blank', clazz: "hub-icon github-contact" }} linkData={{
iconData={{ type: 'svg', icon: gitHubIcon, iconClass: 'center' }} href: `https://www.github.com/${userNames.github}`,
textData={{ showIcon: true }} ariaLabel: "Contact through Github",
/> title: "Contact through Github",
target: "_blank",
clazz: "hub-icon github-contact",
}}
iconData={{ type: "svg", icon: gitHubIcon, iconClass: "center" }}
textData={{ showIcon: true }}
/>
{/if} {/if}
{#if showEmail && userNames?.email} {#if showEmail && userNames?.email}
<ExternalLink <ExternalLink
linkData={{ href: `mailto:${userNames.email}`, ariaLabel: 'Contact by email', title: 'Contact by email', target: '_blank', clazz: "hub-icon email-contact" }} linkData={{
iconData={{ type: 'icon', icon: Mail, iconClass: 'center' }} href: `mailto:${userNames.email}`,
ariaLabel: "Contact by email",
title: "Contact by email",
target: "_blank",
clazz: "hub-icon email-contact",
}}
iconData={{ type: "icon", icon: Mail, iconClass: "center" }}
textData={{ showIcon: true }} textData={{ showIcon: true }}
/> />
{/if} {/if}

View file

@ -0,0 +1,29 @@
<script module lang="ts">
import { defineMeta } from "@storybook/addon-svelte-csf";
import ExternalLink from "./ExternalLink.svelte";
import { blueSkyIcon } from "../util/logoIcons.svelte";
const { Story } = defineMeta({
title: "Components/ExternalLink",
component: ExternalLink,
tags: ["autodocs"],
args: {
linkData: {
href: "https://example.com",
ariaLabel: "Go to example.com",
title: "Example",
target: "_blank",
clazz: "hub-icon bluesky-contact",
},
textData: { text: "Visit Example", location: "left", showIcon: true },
iconData: { type: "svg", icon: blueSkyIcon, iconClass: "center" },
},
argTypes: {
linkData: { control: "object" },
textData: { control: "object" },
iconData: { control: "object" },
},
});
</script>
<Story name="Default" />

View file

@ -52,14 +52,15 @@
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<title>{linkData?.title ?? `Open ${linkData?.ariaLabel} externally`}</title>
{@render (icon as any)()} {@render (icon as any)()}
</svg> </svg>
{:else if type === "icon" && icon} {:else if type === "icon" && icon}
{@const Icon = icon} {@const Icon = icon}
<Icon /> <Icon><title>{linkData?.title ?? `Open ${linkData?.ariaLabel} externally`}</title></Icon>
{:else} {:else}
{@const Icon = ExternalLink} {@const Icon = ExternalLink}
<Icon /> <Icon><title>{linkData?.title ?? `Open ${linkData?.ariaLabel} externally`}</title></Icon>
{/if} {/if}
{/snippet} {/snippet}

View file

@ -0,0 +1,29 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import LazyImage from './LazyImage.svelte';
const sampleSrc = {
img: { src: 'https://picsum.photos/400/300', w: 400, h: 300 },
sources: {
avif: 'https://picsum.photos/400/300.avif',
webp: 'https://picsum.photos/400/300.webp',
jpg: 'https://picsum.photos/400/300.jpg'
}
};
const { Story } = defineMeta({
title: 'Components/LazyImage',
component: LazyImage,
tags: ['autodocs'],
args: { clazz: 'demo-image', src: sampleSrc, alt: 'Random image', style: '', loading: 'lazy' },
argTypes: {
clazz: { control: 'text' },
src: { control: 'object' },
alt: { control: 'text' },
style: { control: 'text' },
loading: { control: { type: 'select' }, options: ['lazy', 'eager'] }
}
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Link from './Link.svelte';
const { Story } = defineMeta({
title: 'Components/Link',
component: Link,
tags: ['autodocs']
});
</script>
<Story name="Default">
<Link href="/" ariaLabel="Example link">
<span>Go Home</span>
</Link>
</Story>

View file

@ -0,0 +1,20 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Pagination from './Pagination.svelte';
const { Story } = defineMeta({
title: 'Components/Pagination',
component: Pagination,
tags: ['autodocs'],
args: { additionalClasses: '', pageSize: 10, totalCount: 50, currentPage: 1, base: '/page' },
argTypes: {
additionalClasses: { control: 'text' },
pageSize: { control: { type: 'number', min: 1, step: 1 } },
totalCount: { control: { type: 'number', min: 0, step: 1 } },
currentPage: { control: { type: 'number', min: 1, step: 1 } },
base: { control: 'text' }
}
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,14 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Tag from './Tag.svelte';
const { Story } = defineMeta({
title: 'Components/Tag',
component: Tag,
tags: ['autodocs'],
args: { name: 'JavaScript' },
argTypes: { name: { control: 'text' } }
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,12 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Footer from './index.svelte';
const { Story } = defineMeta({
title: 'Components/Footer',
component: Footer,
tags: ['autodocs']
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,12 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Header from './index.svelte';
const { Story } = defineMeta({
title: 'Components/Header',
component: Header,
tags: ['autodocs']
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,12 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Logo from './index.svelte';
const { Story } = defineMeta({
title: 'Components/Logo',
component: Logo,
tags: ['autodocs']
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,12 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Nav from './index.svelte';
const { Story } = defineMeta({
title: 'Components/Nav',
component: Nav,
tags: ['autodocs']
});
</script>
<Story name="Default" />

View file

@ -0,0 +1,30 @@
<script context="module" lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import SocialImageCard from './socialImageCard.svelte';
const { Story } = defineMeta({
title: 'Components/SocialImageCard',
component: SocialImageCard,
tags: ['autodocs'],
args: {
header: 'Bradley Shellnut',
page: 'Home',
image: '/b_shell_nut_favicon.png',
content: 'Welcome to my site',
width: 800,
height: 418,
url: 'https://bradleyshellnut.com'
},
argTypes: {
header: { control: 'text' },
page: { control: 'text' },
image: { control: 'text' },
content: { control: 'text' },
width: { control: { type: 'number', min: 100, step: 10 } },
height: { control: { type: 'number', min: 100, step: 10 } },
url: { control: 'text' }
}
});
</script>
<Story name="Default" />