diff --git a/packages/starpod/README.md b/packages/starpod/README.md index 1dc8426..b85655a 100644 --- a/packages/starpod/README.md +++ b/packages/starpod/README.md @@ -90,6 +90,8 @@ validates your config and lists every problem with its field path. - `rssFeed` — the feed your episodes are read from at build time - `links` (optional) — extra navigation links after About and Contact, e.g. `[{ label: 'Store', href: 'https://example.com/store' }]` +- `brandColor` (optional) — hex accent color for the Safari pinned-tab icon + and Windows tile, e.g. `'#531b3c'` ## Integration options diff --git a/packages/starpod/src/components/Breadcrumbs.astro b/packages/starpod/src/components/Breadcrumbs.astro index 5424bba..8a8a2de 100644 --- a/packages/starpod/src/components/Breadcrumbs.astro +++ b/packages/starpod/src/components/Breadcrumbs.astro @@ -9,23 +9,37 @@ export interface Props { const { title } = Astro.props; +// Multi-segment paths (e.g. a custom /collections/[slug] page) get an +// intermediate crumb per ancestor segment, named by humanizing the segment. +// The injected core routes are all single-segment, so those keep the +// classic Home > title trail. +function humanize(segment: string): string { + return segment + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +const segments = url.pathname.split('/').filter(Boolean); + +const breadcrumbItems: Array<{ name: string; href: string }> = [ + { name: 'Home', href: '/' }, + ...segments.slice(0, -1).map((segment, index) => ({ + name: humanize(segment), + href: '/' + segments.slice(0, index + 1).join('/') + })), + ...(segments.length > 0 ? [{ name: title, href: url.pathname }] : []) +]; + const breadcrumbSchema = { '@context': 'https://schema.org', '@type': 'BreadcrumbList', - itemListElement: [ - { - '@type': 'ListItem', - position: 1, - name: 'Home', - item: Astro.site?.toString() || '/' - }, - { - '@type': 'ListItem', - position: 2, - name: title, - item: new URL(url, Astro.site).toString() - } - ] + itemListElement: breadcrumbItems.map((item, index) => ({ + '@type': 'ListItem', + position: index + 1, + name: item.name, + item: new URL(item.href, Astro.site).toString() + })) }; --- @@ -33,31 +47,42 @@ const breadcrumbSchema = { diff --git a/packages/starpod/src/layouts/Layout.astro b/packages/starpod/src/layouts/Layout.astro index ba36cc0..3e9f07f 100644 --- a/packages/starpod/src/layouts/Layout.astro +++ b/packages/starpod/src/layouts/Layout.astro @@ -67,8 +67,15 @@ const description = Astro.props.description ?? starpodConfig.description; - - + + ; + /** + * Hex accent color used for the Safari pinned-tab icon and the Windows + * tile, e.g. "#531b3c". Defaults to the favicon-generator template colors. + */ + brandColor?: string; }; const urlField = (name: string) => @@ -132,7 +138,13 @@ const StarpodConfigSchema: GenericSchema = object({ 'platforms is required (an empty object is fine)' ), rssFeed: urlField('rssFeed'), - links: optional(array(NavLinkSchema, 'links must be an array')) + links: optional(array(NavLinkSchema, 'links must be an array')), + brandColor: optional( + pipe( + string('brandColor must be a string'), + hexColor('brandColor must be a hex color like #531b3c') + ) + ) }); /** diff --git a/tests/unit/config.test.ts b/tests/unit/config.test.ts index a31af24..71175e3 100644 --- a/tests/unit/config.test.ts +++ b/tests/unit/config.test.ts @@ -28,6 +28,17 @@ describe('defineStarpodConfig', () => { expect(config.links).toHaveLength(1); }); + it('accepts a hex brandColor and rejects a non-hex one', () => { + const config = defineStarpodConfig({ + ...validConfig, + brandColor: '#531b3c' + }); + expect(config.brandColor).toBe('#531b3c'); + expect(() => + validateStarpodConfig({ ...validConfig, brandColor: 'maroon' }) + ).toThrow(/brandColor must be a hex color/); + }); + it('rejects a non-URL rssFeed and names the field', () => { expect(() => validateStarpodConfig({ ...validConfig, rssFeed: 'not a url' })