diff options
author | Cédric Bontems | 2023-11-01 23:16:01 +0100 |
---|---|---|
committer | GitHub | 2023-11-01 23:16:01 +0100 |
commit | 72cca2d07644f00595da6ebf7d603adb282f359d (patch) | |
tree | 1465570ccb8146c497c699125eca7d7e296e63b3 | |
parent | dcb5f7aae1650a60731e27cd13a6f5ac015cd1e9 (diff) | |
download | IT.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.md | 12 | ||||
-rw-r--r-- | docs/src/content/docs/reference/frontmatter.md | 40 | ||||
-rw-r--r-- | packages/starlight/components/Hero.astro | 30 | ||||
-rw-r--r-- | packages/starlight/components/SiteTitle.astro | 10 | ||||
-rw-r--r-- | packages/starlight/schema.ts | 58 | ||||
-rw-r--r-- | packages/starlight/schemas/hero.ts | 70 | ||||
-rw-r--r-- | packages/starlight/style/util.css | 6 |
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. |