Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for light / dark hero images #280

Merged
merged 15 commits into from
Nov 1, 2023

Conversation

cbontems
Copy link
Contributor

What kind of changes does this PR include?

  • Changes to Starlight code

Description

  • Modifies the hero image schema to add 2 optional images properties : dark and light.
  • Modifies the Hero component to display an hero image with the following logic:
    • if a file property is set, use it for all modes
    • if dark and light properties are set, use them accordingly
    • if neither file nor dark are set, and html is set, use raw html
    • otherwise, display nothing
  • Modifies the frontmatter.md reference to reflect the changes.

note
I have been considering allowing for darkHtml and lightHtml in the same way, but finally thought it might be too much for most users. And I guess people using raw html know how to handle this. What to you think ?

@changeset-bot
Copy link

changeset-bot bot commented Jun 30, 2023

🦋 Changeset detected

Latest commit: 383c376

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@astrojs/starlight Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify
Copy link

netlify bot commented Jun 30, 2023

Deploy Preview for astro-starlight ready!

Name Link
🔨 Latest commit 383c376
🔍 Latest deploy log https://app.netlify.com/sites/astro-starlight/deploys/6542cc1eabb531000854badf
😎 Deploy Preview https://deploy-preview-280--astro-starlight.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 100 (no change from production)
Accessibility: 100 (no change from production)
Best Practices: 100 (no change from production)
SEO: 92 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify site configuration.

@github-actions github-actions bot added 📚 docs Documentation website changes 🌟 core Changes to Starlight’s main package labels Jun 30, 2023
Copy link
Contributor

@lorenzolewis lorenzolewis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's been quite a few changes to the codebase this PR has a few conflicts. I'm not sure of the best way to communicate those so I've created a diff that I'll include below. With that diff applied and then the suggestion on updating the docs phrasing this one looks good to me 🥳

diff --git a/packages/starlight/components/Hero.astro b/packages/starlight/components/Hero.astro
index 1fccc61..fbf56b8 100644
--- a/packages/starlight/components/Hero.astro
+++ b/packages/starlight/components/Hero.astro
@@ -4,160 +4,146 @@ import { Image } from 'astro:assets';
 import CallToAction from './CallToAction.astro';
 
 interface Props {
-  fallbackTitle: string;
-  hero: NonNullable<CollectionEntry<'docs'>['data']['hero']>;
+	fallbackTitle: string;
+	hero: NonNullable<CollectionEntry<'docs'>['data']['hero']>;
 }
 
-const {
-  title = Astro.props.fallbackTitle,
-  tagline,
-  image,
-  actions,
-} = Astro.props.hero;
+const { title = Astro.props.fallbackTitle, tagline, image, actions } = Astro.props.hero;
 
 const imageAttrs = {
-  loading: 'eager' as const,
-  decoding: 'async' as const,
-  width: 400,
-  height: 400,
-  alt: image?.alt,
+	loading: 'eager' as const,
+	decoding: 'async' as const,
+	width: 400,
+	height: 400,
+	alt: image?.alt,
 };
 // darkImage is the default and uses either file, dark or raw html
 const darkImage = image?.file ? image.file : image?.dark ? image.dark : null;
 // lightImage is only used if darkImage is also used
-const lightImage = image?.file ? null : (image?.dark && image?.light) ? image.light : null;
+const lightImage = image?.file ? null : image?.dark && image?.light ? image.light : null;
 // rawHtml is only used if darkImage is not used
 const rawHtml = !darkImage && image?.html ? image.html : null;
 ---
 
 <div class="hero">
-
-  {
-    darkImage && (
-    darkImage.format === 'svg' ? (
-        <img src={darkImage.src} {...imageAttrs} class:list={{ 'dark-only': Boolean(lightImage) }}/>
-      ) : (
-        <Image src={darkImage} {...imageAttrs} class:list={{ 'dark-only': Boolean(lightImage) }}/>
-      )
-    ) 
-  }
-  {
-    lightImage && (
-    lightImage.format === 'svg' ? (
-        <img src={lightImage.src} {...imageAttrs} class="light-only"/>
-      ) : (
-        <Image src={lightImage} {...imageAttrs} class="light-only"/>
-      )
-    ) 
-  }
-{
-  rawHtml && <div class="hero-html flex" set:html={rawHtml} />
-}
-  <div class="flex stack">
-    <div class="flex copy">
-      <h1 id="_top" data-page-title set:html={title} />
-      {tagline && <div class="tagline" set:html={tagline} />}
-    </div>
-    {
-      actions.length > 0 && (
-        <div class="flex actions">
-          {actions.map(({ text, ...attrs }) => (
-            <CallToAction {...attrs} set:html={text} />
-          ))}
-        </div>
-      )
-    }
-  </div>
+	{
+		darkImage &&
+			(darkImage.format === 'svg' ? (
+				<img
+					src={darkImage.src}
+					{...imageAttrs}
+					class:list={{ 'dark-only': Boolean(lightImage) }}
+				/>
+			) : (
+				<Image src={darkImage} {...imageAttrs} class:list={{ 'dark-only': Boolean(lightImage) }} />
+			))
+	}
+	{
+		lightImage &&
+			(lightImage.format === 'svg' ? (
+				<img src={lightImage.src} {...imageAttrs} class="light-only" />
+			) : (
+				<Image src={lightImage} {...imageAttrs} class="light-only" />
+			))
+	}
+	{rawHtml && <div class="hero-html sl-flex" set:html={rawHtml} />}
+	<div class="sl-flex stack">
+		<div class="sl-flex copy">
+			<h1 id="_top" data-page-title set:html={title} />
+			{tagline && <div class="tagline" set:html={tagline} />}
+		</div>
+		{
+			actions.length > 0 && (
+				<div class="sl-flex actions">
+					{actions.map(({ text, ...attrs }) => (
+						<CallToAction {...attrs} set:html={text} />
+					))}
+				</div>
+			)
+		}
+	</div>
 </div>
 
 <style>
-  .hero {
-    display: grid;
-    align-items: center;
-    gap: 1rem;
-    padding-bottom: 1rem;
-  }
-
-  .hero > img,
-  .hero > .hero-html {
-    object-fit: contain;
-    width: min(70%, 20rem);
-    height: auto;
-    margin-inline: auto;
-  }
-
-  :global([data-theme='light']) .dark-only {
-    display: none;
-  }
-  :global([data-theme='dark']) .light-only {
-    display: none;
-  }
-
-  .stack {
-    flex-direction: column;
-    gap: clamp(1.5rem, calc(1.5rem + 1vw), 2rem);
-    text-align: center;
-  }
-
-  .copy {
-    flex-direction: column;
-    gap: 1rem;
-    align-items: center;
-  }
-
-  .copy > * {
-    max-width: 50ch;
-  }
-
-  h1 {
-    font-size: clamp(
-      var(--sl-text-3xl),
-      calc(0.25rem + 5vw),
-      var(--sl-text-6xl)
-    );
-    line-height: var(--sl-line-height-headings);
-    font-weight: 600;
-    color: var(--sl-color-white);
-  }
-
-  .tagline {
-    font-size: clamp(
-      var(--sl-text-base),
-      calc(0.0625rem + 2vw),
-      var(--sl-text-xl)
-    );
-    color: var(--sl-color-gray-2);
-  }
-
-  .actions {
-    gap: 1rem 2rem;
-    flex-wrap: wrap;
-    justify-content: center;
-  }
-
-  @media (min-width: 50rem) {
-    .hero {
-      grid-template-columns: 7fr 4fr;
-      gap: 3%;
-      padding-block: clamp(2.5rem, calc(1rem + 10vmin), 10rem);
-    }
-
-    .hero > img,
-    .hero > .hero-html {
-      order: 2;
-      width: min(100%, 25rem);
-    }
-
-    .stack {
-      text-align: start;
-    }
-
-    .copy {
-      align-items: flex-start;
-    }
-
-    .actions {
-      justify-content: flex-start;
-    }
-  }
+	.hero {
+		display: grid;
+		align-items: center;
+		gap: 1rem;
+		padding-bottom: 1rem;
+	}
+
+	.hero > img,
+	.hero > .hero-html {
+		object-fit: contain;
+		width: min(70%, 20rem);
+		height: auto;
+		margin-inline: auto;
+	}
+
+	:global([data-theme='light']) .dark-only {
+		display: none;
+	}
+	:global([data-theme='dark']) .light-only {
+		display: none;
+	}
+
+	.stack {
+		flex-direction: column;
+		gap: clamp(1.5rem, calc(1.5rem + 1vw), 2rem);
+		text-align: center;
+	}
+
+	.copy {
+		flex-direction: column;
+		gap: 1rem;
+		align-items: center;
+	}
+
+	.copy > * {
+		max-width: 50ch;
+	}
+
+	h1 {
+		font-size: clamp(var(--sl-text-3xl), calc(0.25rem + 5vw), var(--sl-text-6xl));
+		line-height: var(--sl-line-height-headings);
+		font-weight: 600;
+		color: var(--sl-color-white);
+	}
+
+	.tagline {
+		font-size: clamp(var(--sl-text-base), calc(0.0625rem + 2vw), var(--sl-text-xl));
+		color: var(--sl-color-gray-2);
+	}
+
+	.actions {
+		gap: 1rem 2rem;
+		flex-wrap: wrap;
+		justify-content: center;
+	}
+
+	@media (min-width: 50rem) {
+		.hero {
+			grid-template-columns: 7fr 4fr;
+			gap: 3%;
+			padding-block: clamp(2.5rem, calc(1rem + 10vmin), 10rem);
+		}
+
+		.hero > img,
+		.hero > .hero-html {
+			order: 2;
+			width: min(100%, 25rem);
+		}
+
+		.stack {
+			text-align: start;
+		}
+
+		.copy {
+			align-items: flex-start;
+		}
+
+		.actions {
+			justify-content: flex-start;
+		}
+	}
 </style>
diff --git a/packages/starlight/schema.ts b/packages/starlight/schema.ts
index 320b30b..5345c46 100644
--- a/packages/starlight/schema.ts
+++ b/packages/starlight/schema.ts
@@ -1,121 +1,177 @@
-import { z } from "astro/zod";
-import { HeadConfigSchema } from "./schemas/head";
-import { TableOfContentsSchema } from "./schemas/tableOfContents";
-import { Icons } from "./components/Icons";
-export { i18nSchema } from "./schemas/i18n";
+import { z } from 'astro/zod';
+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';
+export { i18nSchema } from './schemas/i18n';
 
 type IconName = keyof typeof Icons;
 const iconNames = Object.keys(Icons) as [IconName, ...IconName[]];
 
 type ImageFunction = () => z.ZodObject<{
-  src: z.ZodString;
-  width: z.ZodNumber;
-  height: z.ZodNumber;
-  format: z.ZodUnion<
-    [
-      z.ZodLiteral<"png">,
-      z.ZodLiteral<"jpg">,
-      z.ZodLiteral<"jpeg">,
-      z.ZodLiteral<"tiff">,
-      z.ZodLiteral<"webp">,
-      z.ZodLiteral<"gif">,
-      z.ZodLiteral<"svg">
-    ]
-  >;
+	src: z.ZodString;
+	width: z.ZodNumber;
+	height: z.ZodNumber;
+	format: z.ZodUnion<
+		[
+			z.ZodLiteral<'png'>,
+			z.ZodLiteral<'jpg'>,
+			z.ZodLiteral<'jpeg'>,
+			z.ZodLiteral<'tiff'>,
+			z.ZodLiteral<'webp'>,
+			z.ZodLiteral<'gif'>,
+			z.ZodLiteral<'svg'>,
+		]
+	>;
 }>;
 
 export function docsSchema() {
-  return ({ image }: { image: ImageFunction }) =>
-    z.object({
-      /** The title of the current page. Required. */
-      title: z.string(),
-
-      /**
-       * A short description of the current page’s content. Optional, but recommended.
-       * A good description is 150–160 characters long and outlines the key content
-       * of the page in a clear and engaging way.
-       */
-      description: z.string().optional(),
-
-      /**
-       * Custom URL where a reader can edit this page.
-       * Overrides the `editLink.baseUrl` global config if set.
-       *
-       * Can also be set to `false` to disable showing an edit link on this page.
-       */
-      editUrl: z
-        .union([z.string().url(), z.boolean()])
-        .optional()
-        .default(true),
-
-      /** Set custom `<head>` tags just for this page. */
-      head: HeadConfigSchema(),
-
-      /** Override global table of contents configuration for this page. */
-      tableOfContents: TableOfContentsSchema().optional(),
-
-      /**
-       * Set the layout style for this page.
-       * Can be `'doc'` (the default) or `'splash'` for a wider layout without any sidebars.
-       */
-      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(),
-                /** Relative path to an image file in your repo to use in dark mode, e.g. `../../assets/hero-dark.png`. */
-                dark: image().optional(),
-                /** Relative path to an image file in your repo to use in light mode, e.g. `../../assets/hero-light.png`. */
-                light: 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(),
-    });
+	return ({ image }: { image: ImageFunction }) =>
+		z.object({
+			/** The title of the current page. Required. */
+			title: z.string(),
+
+			/**
+			 * A short description of the current page’s content. Optional, but recommended.
+			 * A good description is 150–160 characters long and outlines the key content
+			 * of the page in a clear and engaging way.
+			 */
+			description: z.string().optional(),
+
+			/**
+			 * Custom URL where a reader can edit this page.
+			 * Overrides the `editLink.baseUrl` global config if set.
+			 *
+			 * Can also be set to `false` to disable showing an edit link on this page.
+			 */
+			editUrl: z.union([z.string().url(), z.boolean()]).optional().default(true),
+
+			/** Set custom `<head>` tags just for this page. */
+			head: HeadConfigSchema(),
+
+			/** Override global table of contents configuration for this page. */
+			tableOfContents: TableOfContentsSchema().optional(),
+
+			/**
+			 * Set the layout style for this page.
+			 * Can be `'doc'` (the default) or `'splash'` for a wider layout without any sidebars.
+			 */
+			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`. */
+					/** 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(),
+							/** Relative path to an image file in your repo to use in dark mode, e.g. `../../assets/hero-dark.png`. */
+							dark: image().optional(),
+							/** Relative path to an image file in your repo to use in light mode, e.g. `../../assets/hero-light.png`. */
+							light: 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(),
+
+			/**
+			 * The last update date of the current page.
+			 * Overrides the `lastUpdated` global config or the date generated from the Git history.
+			 */
+			lastUpdated: z.union([z.date(), z.boolean()]).optional(),
+
+			/**
+			 * The previous navigation link configuration.
+			 * Overrides the `pagination` global config or the link text and/or URL.
+			 */
+			prev: PrevNextLinkConfigSchema(),
+			/**
+			 * The next navigation link configuration.
+			 * Overrides the `pagination` global config or the link text and/or URL.
+			 */
+			next: PrevNextLinkConfigSchema(),
+
+			sidebar: z
+				.object({
+					/**
+					 * The order of this page in the navigation.
+					 * Pages are sorted by this value in ascending order. Then by slug.
+					 * If not provided, pages will be sorted alphabetically by slug.
+					 * If two pages have the same order value, they will be sorted alphabetically by slug.
+					 */
+					order: z.number().optional(),
+
+					/**
+					 * The label for this page in the navigation.
+					 * Defaults to the page `title` if not set.
+					 */
+					label: z.string().optional(),
+
+					/**
+					 * Prevents this page from being included in autogenerated sidebar groups.
+					 */
+					hidden: z.boolean().default(false),
+					/**
+					 * Adds a badge to the sidebar link.
+					 * Can be a string or an object with a variant and text.
+					 * Variants include 'note', 'tip', 'caution', 'danger', 'success', and 'default'.
+					 * Passing only a string defaults to the 'default' variant which uses the site accent color.
+					 */
+					badge: BadgeConfigSchema(),
+				})
+				.default({}),
+
+			/** Display an announcement banner at the top of this page. */
+			banner: z
+				.object({
+					/** The content of the banner. Supports HTML syntax. */
+					content: z.string(),
+				})
+				.optional(),
+
+			/** Pagefind indexing for this page - set to false to disable. */
+			pagefind: z.boolean().default(true),
+		});
 }

@lorenzolewis
Copy link
Contributor

Oh, and one last thing I forgot is to mention generating a changeset for this one.

@vercel
Copy link

vercel bot commented Oct 5, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
starlight ✅ Ready (Inspect) Visit Preview Oct 6, 2023 3:17pm

@delucis
Copy link
Member

delucis commented Oct 26, 2023

Took a pass at getting this cleaned up so we can finally ship it! Quick summary of the changes I made:

  • Made the light/dark styles into global CSS utilities so we can share them between the site title and the hero
  • Made the hero.image config a union of three types instead of lots of optional types. Valid config is now { file; alt? } or { light; dark; alt? } or { html }. I also updated the reference docs to reflect that.

Thanks again for everyone’s patience on this one!

Copy link
Member

@HiDeoo HiDeoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing update to make it to the finish line. 👏

The code is very clean, I love the stricter schema. Also tested locally and it works great.

I guess we could potentially have a component to render either <Image/> or <img/> to avoid some repetition, but it happens only 2 times in a single component so it may be better to keep it as is.

This looks good to me 🚀 (altho I almost feel a bit bad with the new alt documentation that we do not use it in our index page 😅)

Great work everyone.

@delucis
Copy link
Member

delucis commented Oct 26, 2023

I almost feel a bit bad with the new alt documentation that we do not use it in our index page 😅

alt defaults to "", hiding the image from assistive tech, so I think that’s OK for our hero section where the star is decorative, but yeah definitely wouldn’t be if someone’s hero image contained come more informative content.

I guess we could potentially have a component to render either <Image/> or <img/> to avoid some repetition, but it happens only 2 times in a single component so it may be better to keep it as is.

Oh, actually, I should double check. I think <Image /> now allows passing an SVG? We might be able to clean that up in a follow-up PR assuming it was in 3.2 or below.

Edit: Will run a test, but the change I was thinking of released in 2.8.3, so would definitely be in scope: withastro/astro#7643

`<Image />` supports SVGs since Astro v2.8.3
@HiDeoo
Copy link
Member

HiDeoo commented Oct 26, 2023

Oh, actually, I should double check. I think <Image /> now allows passing an SVG? We might be able to clean that up in a follow-up PR assuming it was in 3.2 or below.

Edit: Will run a test, but the change I was thinking of released in 2.8.3, so would definitely be in scope: withastro/astro#7643

Oh, totally missed that one, would be great indeed.

@delucis
Copy link
Member

delucis commented Oct 26, 2023

Update: Yup! <Image /> is fine with SVGs now, so that’s a really nice thing to clean up 🎉

@delucis delucis added the 🌟 minor Change that triggers a minor release label Oct 27, 2023
@lorenzolewis
Copy link
Contributor

The only minor feedback I have is that LogoConfig uses src instead of file that is used for this, so that's slightly inconsistent. Other than that it lgtm!

@delucis
Copy link
Member

delucis commented Oct 31, 2023

The only minor feedback I have is that LogoConfig uses src instead of file that is used for this, so that's slightly inconsistent. Other than that it lgtm!

Yes, that’s true (also the case with the current hero image support). It was chosen to distinguish file from html, although maybe both logo and hero should in fact support the same here. Maybe worth a follow-up discussion? Don’t mind us revisiting that API naming and making a breaking change later if it makes sense.

@delucis delucis merged commit 72cca2d into withastro:main Nov 1, 2023
@astrobot-houston astrobot-houston mentioned this pull request Nov 1, 2023
HiDeoo added a commit to HiDeoo/starlight that referenced this pull request Nov 2, 2023
* main: (22 commits)
  fix(docs-i18n-tracker): update `translations` import (withastro#1025)
  [ci] format
  i18n(zh-cn): Update css-and-tailwind.mdx (withastro#1018)
  [ci] format
  i18n(zh-cn): Update authoring-content.md (withastro#1016)
  i18n(ko-KR): update `configuration.mdx` (withastro#1015)
  i18n(ko-KR): update `sidebar.mdx` (withastro#1014)
  i18n(ko-KR): update `i18n.mdx` (withastro#1013)
  [ci] format
  i18n(ko-KR): update `frontmatter.md` (withastro#1017)
  [ci] format
  i18n(pt-BR): Update `css-and-tailwind.mdx`, `authoring-content.md` and `overrides.md` (withastro#1009)
  [ci] format
  [ci] release (withastro#996)
  Fix Prettier-compatibility of i18n test fixture
  Refactor translation system to be reusable in non-Astro code (withastro#1003)
  Add social icons to mobile menu footer (withastro#988)
  [ci] format
  Add Galician language support (withastro#1004)
  feat: add support for light / dark hero images (withastro#280)
  ...
vasfvitor added a commit to vasfvitor/astro-starlight that referenced this pull request Nov 7, 2023
delucis pushed a commit that referenced this pull request Nov 9, 2023
* Update frontmatter.md

#280

* Update frontmatter.md

* Update frontmatter.md

* improve wording
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🌟 core Changes to Starlight’s main package 📚 docs Documentation website changes 🌟 minor Change that triggers a minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants