summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCédric Bontems2023-11-01 23:16:01 +0100
committerGitHub2023-11-01 23:16:01 +0100
commit72cca2d07644f00595da6ebf7d603adb282f359d (patch)
tree1465570ccb8146c497c699125eca7d7e296e63b3
parentdcb5f7aae1650a60731e27cd13a6f5ac015cd1e9 (diff)
downloadIT.starlight-72cca2d07644f00595da6ebf7d603adb282f359d.tar.gz
IT.starlight-72cca2d07644f00595da6ebf7d603adb282f359d.tar.bz2
IT.starlight-72cca2d07644f00595da6ebf7d603adb282f359d.zip
feat: add support for light / dark hero images (#280)
Co-authored-by: Lorenzo Lewis <lorenzo_lewis@icloud.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
-rw-r--r--.changeset/violet-berries-mix.md12
-rw-r--r--docs/src/content/docs/reference/frontmatter.md40
-rw-r--r--packages/starlight/components/Hero.astro30
-rw-r--r--packages/starlight/components/SiteTitle.astro10
-rw-r--r--packages/starlight/schema.ts58
-rw-r--r--packages/starlight/schemas/hero.ts70
-rw-r--r--packages/starlight/style/util.css6
7 files changed, 147 insertions, 79 deletions
diff --git a/.changeset/violet-berries-mix.md b/.changeset/violet-berries-mix.md
new file mode 100644
index 00000000..e93a59f8
--- /dev/null
+++ b/.changeset/violet-berries-mix.md
@@ -0,0 +1,12 @@
+---
+'@astrojs/starlight': minor
+---
+
+Support light & dark variants of the hero image.
+
+⚠️ **Potentially breaking change:** The `hero.image` schema is now slightly stricter than previously.
+
+The `hero.image.html` property can no longer be used alongside the `hero.image.alt` or `hero.image.file` properties.
+Previously, `html` was ignored when used with `file` and `alt` was ignored when used with `html`.
+Now, those combinations will throw errors.
+If you encounter errors, remove the `image.hero` property that is not in use. \ No newline at end of file
diff --git a/docs/src/content/docs/reference/frontmatter.md b/docs/src/content/docs/reference/frontmatter.md
index 9b401383..e1adddb5 100644
--- a/docs/src/content/docs/reference/frontmatter.md
+++ b/docs/src/content/docs/reference/frontmatter.md
@@ -111,20 +111,44 @@ hero:
---
```
+You can display different versions of the hero image in light and dark modes.
+
+```md
+---
+hero:
+ image:
+ alt: A glittering, brightly colored logo
+ dark: ../../assets/logo-dark.png
+ light: ../../assets/logo-light.png
+---
+```
+
#### `HeroConfig`
```ts
interface HeroConfig {
title?: string;
tagline?: string;
- image?: {
- alt?: string;
- // Relative path to an image in your repository.
- file?: string;
- // Raw HTML to use in the image slot.
- // Could be a custom `<img>` tag or inline `<svg>`.
- html?: string;
- };
+ image?:
+ | {
+ // Relative path to an image in your repository.
+ file: string;
+ // Alt text to make the image accessible to assistive technology
+ alt?: string;
+ }
+ | {
+ // Relative path to an image in your repository to be used for dark mode.
+ dark: string;
+ // Relative path to an image in your repository to be used for light mode.
+ light: string;
+ // Alt text to make the image accessible to assistive technology
+ alt?: string;
+ }
+ | {
+ // Raw HTML to use in the image slot.
+ // Could be a custom `<img>` tag or inline `<svg>`.
+ html: string;
+ };
actions?: Array<{
text: string;
link: string;
diff --git a/packages/starlight/components/Hero.astro b/packages/starlight/components/Hero.astro
index bd9e8202..f763ce22 100644
--- a/packages/starlight/components/Hero.astro
+++ b/packages/starlight/components/Hero.astro
@@ -14,20 +14,34 @@ const imageAttrs = {
height: 400,
alt: image?.alt || '',
};
+
+let darkImage: ImageMetadata | undefined;
+let lightImage: ImageMetadata | undefined;
+let rawHtml: string | undefined;
+if (image) {
+ if ('file' in image) {
+ darkImage = image.file;
+ } else if ('dark' in image) {
+ darkImage = image.dark;
+ lightImage = image.light;
+ } else {
+ rawHtml = image.html;
+ }
+}
---
<div class="hero">
{
- image?.file ? (
- image.file.format === 'svg' ? (
- <img src={image.file.src} {...imageAttrs} />
- ) : (
- <Image src={image.file} {...imageAttrs} />
- )
- ) : (
- image?.html && <div class="hero-html sl-flex" set:html={image.html} />
+ darkImage && (
+ <Image
+ src={darkImage}
+ {...imageAttrs}
+ class:list={{ 'light:sl-hidden': Boolean(lightImage) }}
+ />
)
}
+ {lightImage && <Image src={lightImage} {...imageAttrs} class="dark:sl-hidden" />}
+ {rawHtml && <div class="hero-html sl-flex" set:html={rawHtml} />}
<div class="sl-flex stack">
<div class="sl-flex copy">
<h1 id={PAGE_TITLE_ID} data-page-title set:html={title} />
diff --git a/packages/starlight/components/SiteTitle.astro b/packages/starlight/components/SiteTitle.astro
index bb1c2038..afb09c80 100644
--- a/packages/starlight/components/SiteTitle.astro
+++ b/packages/starlight/components/SiteTitle.astro
@@ -12,7 +12,7 @@ const href = pathWithBase(Astro.props.locale || '/');
config.logo && logos.dark && (
<>
<img
- class:list={{ 'dark-only': !('src' in config.logo) }}
+ class:list={{ 'light:sl-hidden': !('src' in config.logo) }}
alt={config.logo.alt}
src={logos.dark.src}
width={logos.dark.width}
@@ -21,7 +21,7 @@ const href = pathWithBase(Astro.props.locale || '/');
{/* Show light alternate if a user configure both light and dark logos. */}
{!('src' in config.logo) && (
<img
- class="light-only"
+ class="dark:sl-hidden"
alt={config.logo.alt}
src={logos.light?.src}
width={logos.light?.width}
@@ -56,10 +56,4 @@ const href = pathWithBase(Astro.props.locale || '/');
object-fit: contain;
object-position: 0 50%;
}
- :global([data-theme='light']) .dark-only {
- display: none;
- }
- :global([data-theme='dark']) .light-only {
- display: none;
- }
</style>
diff --git a/packages/starlight/schema.ts b/packages/starlight/schema.ts
index 5f73dfdc..cece5949 100644
--- a/packages/starlight/schema.ts
+++ b/packages/starlight/schema.ts
@@ -3,16 +3,13 @@ import type { SchemaContext } from 'astro:content';
import { HeadConfigSchema } from './schemas/head';
import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
import { TableOfContentsSchema } from './schemas/tableOfContents';
-import { Icons } from './components/Icons';
import { BadgeConfigSchema } from './schemas/badge';
+import { HeroSchema } from './schemas/hero';
import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
export { i18nSchema } from './schemas/i18n';
-type IconName = keyof typeof Icons;
-const iconNames = Object.keys(Icons) as [IconName, ...IconName[]];
-
export function docsSchema() {
- return ({ image }: SchemaContext) =>
+ return (context: SchemaContext) =>
z.object({
/** The title of the current page. Required. */
title: z.string(),
@@ -45,56 +42,7 @@ export function docsSchema() {
template: z.enum(['doc', 'splash']).default('doc'),
/** Display a hero section on this page. */
- hero: z
- .object({
- /**
- * The large title text to show. If not provided, will default to the top-level `title`.
- * Can include HTML.
- */
- title: z.string().optional(),
- /**
- * A short bit of text about your project.
- * Will be displayed in a smaller size below the title.
- */
- tagline: z.string().optional(),
- /** The image to use in the hero. You can provide either a relative `file` path or raw `html`. */
- image: z
- .object({
- /** Alt text for screenreaders and other assistive technologies describing your hero image. */
- alt: z.string().default(''),
- /** Relative path to an image file in your repo, e.g. `../../assets/hero.png`. */
- file: image().optional(),
- /** Raw HTML string instead of an image file. Useful for inline SVGs or more complex hero content. */
- html: z.string().optional(),
- })
- .optional(),
- /** An array of call-to-action links displayed at the bottom of the hero. */
- actions: z
- .object({
- /** Text label displayed in the link. */
- text: z.string(),
- /** Value for the link’s `href` attribute, e.g. `/page` or `https://mysite.com`. */
- link: z.string(),
- /** Button style to use. One of `primary`, `secondary`, or `minimal` (the default). */
- variant: z.enum(['primary', 'secondary', 'minimal']).default('minimal'),
- /**
- * An optional icon to display alongside the link text.
- * Can be an inline `<svg>` or the name of one of Starlight’s built-in icons.
- */
- icon: z
- .union([z.enum(iconNames), z.string().startsWith('<svg')])
- .transform((icon) => {
- const parsedIcon = z.enum(iconNames).safeParse(icon);
- return parsedIcon.success
- ? ({ type: 'icon', name: parsedIcon.data } as const)
- : ({ type: 'raw', html: icon } as const);
- })
- .optional(),
- })
- .array()
- .default([]),
- })
- .optional(),
+ hero: HeroSchema(context).optional(),
/**
* The last update date of the current page.
diff --git a/packages/starlight/schemas/hero.ts b/packages/starlight/schemas/hero.ts
new file mode 100644
index 00000000..e1b5611c
--- /dev/null
+++ b/packages/starlight/schemas/hero.ts
@@ -0,0 +1,70 @@
+import { z } from 'astro/zod';
+import type { SchemaContext } from 'astro:content';
+import { Icons } from '../components/Icons';
+
+type IconName = keyof typeof Icons;
+const iconNames = Object.keys(Icons) as [IconName, ...IconName[]];
+
+export const HeroSchema = ({ image }: SchemaContext) =>
+ z.object({
+ /**
+ * The large title text to show. If not provided, will default to the top-level `title`.
+ * Can include HTML.
+ */
+ title: z.string().optional(),
+ /**
+ * A short bit of text about your project.
+ * Will be displayed in a smaller size below the title.
+ */
+ tagline: z.string().optional(),
+ /** The image to use in the hero. You can provide either a relative `file` path or raw `html`. */
+ image: z
+ .union([
+ z.object({
+ /** Alt text for screenreaders and other assistive technologies describing your hero image. */
+ alt: z.string().default(''),
+ /** Relative path to an image file in your repo, e.g. `../../assets/hero.png`. */
+ file: image(),
+ }),
+ z.object({
+ /** Alt text for screenreaders and other assistive technologies describing your hero image. */
+ alt: z.string().default(''),
+ /** Relative path to an image file in your repo to use in dark mode, e.g. `../../assets/hero-dark.png`. */
+ dark: image(),
+ /** Relative path to an image file in your repo to use in light mode, e.g. `../../assets/hero-light.png`. */
+ light: image(),
+ }),
+ z
+ .object({
+ /** Raw HTML string instead of an image file. Useful for inline SVGs or more complex hero content. */
+ html: z.string(),
+ })
+ .transform(({ html }) => ({ html, alt: '' })),
+ ])
+ .optional(),
+ /** An array of call-to-action links displayed at the bottom of the hero. */
+ actions: z
+ .object({
+ /** Text label displayed in the link. */
+ text: z.string(),
+ /** Value for the link’s `href` attribute, e.g. `/page` or `https://mysite.com`. */
+ link: z.string(),
+ /** Button style to use. One of `primary`, `secondary`, or `minimal` (the default). */
+ variant: z.enum(['primary', 'secondary', 'minimal']).default('minimal'),
+ /**
+ * An optional icon to display alongside the link text.
+ * Can be an inline `<svg>` or the name of one of Starlight’s built-in icons.
+ */
+ icon: z
+ .union([z.enum(iconNames), z.string().startsWith('<svg')])
+ .transform((icon) => {
+ const parsedIcon = z.enum(iconNames).safeParse(icon);
+ return parsedIcon.success
+ ? ({ type: 'icon', name: parsedIcon.data } as const)
+ : ({ type: 'raw', html: icon } as const);
+ })
+ .optional(),
+ })
+ .array()
+ .default([]),
+ });
diff --git a/packages/starlight/style/util.css b/packages/starlight/style/util.css
index b041a9c2..74868532 100644
--- a/packages/starlight/style/util.css
+++ b/packages/starlight/style/util.css
@@ -41,6 +41,12 @@
display: block;
}
}
+[data-theme='light'] .light\:sl-hidden {
+ display: none;
+}
+[data-theme='dark'] .dark\:sl-hidden {
+ display: none;
+}
/*
Flip an element around the y-axis when in an RTL context.