@grove-dev/starlight 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,22 +2,30 @@
2
2
  import { Icon } from '@astrojs/starlight/components';
3
3
  import { Image } from 'astro:assets';
4
4
  import { PAGE_TITLE_ID } from '../../core/config/constants';
5
- import type { HeroLayout } from '../../schema';
5
+ import { warnAboutMissingDocsSchemaOnce } from '../../core/config/docs-schema';
6
6
  import ContainerSection from '../custom/ContainerSection.astro';
7
7
  import LinkButton from '../custom/LinkButton.astro';
8
8
 
9
9
  const { data } = Astro.locals.starlightRoute.entry;
10
- const { title = data.title, tagline: heroTagline, image, actions = [] } = data.hero || {};
11
- const heroLayout = ((data.hero as any)?.layout as HeroLayout) ?? 'centered';
12
- const announcement =
13
- ((data.hero as any)?.announcement as { text: string; link: string }) ?? undefined;
14
- const hasImage = !!image;
10
+
11
+ warnAboutMissingDocsSchemaOnce(data.hero);
12
+
13
+ const {
14
+ title = data.title,
15
+ tagline: heroTagline,
16
+ image,
17
+ announcement,
18
+ actions = [],
19
+ } = data.hero || {};
20
+
21
+ // Falls back on an empty tagline too, not just a missing one.
15
22
  const tagline = heroTagline || data.description;
16
23
 
24
+ const hasImage = !!image;
17
25
  const imageAttrs = {
18
- loading: 'eager' as const,
19
- decoding: 'async' as const,
20
- alt: image?.alt || '',
26
+ loading: 'eager' as const,
27
+ decoding: 'async' as const,
28
+ alt: image?.alt || '',
21
29
  };
22
30
 
23
31
  let darkImage: ImageMetadata | undefined;
@@ -25,19 +33,19 @@ let lightImage: ImageMetadata | undefined;
25
33
  let rawHtml: string | undefined;
26
34
 
27
35
  if (image) {
28
- if ('file' in image) {
29
- darkImage = image.file;
30
- } else if ('dark' in image) {
31
- darkImage = image.dark;
32
- lightImage = image.light;
33
- } else {
34
- rawHtml = image.html;
35
- }
36
+ if ('file' in image) {
37
+ darkImage = image.file;
38
+ } else if ('dark' in image) {
39
+ darkImage = image.dark;
40
+ lightImage = image.light;
41
+ } else {
42
+ rawHtml = image.html;
43
+ }
36
44
  }
37
45
  ---
38
46
 
39
47
  <ContainerSection>
40
- <div class="hero" data-layout={heroLayout} data-has-image={hasImage ? '' : undefined}>
48
+ <div class="hero" data-layout={data.hero?.layout} data-has-image={hasImage ? '' : undefined}>
41
49
  {
42
50
  hasImage && (
43
51
  <div class="hero-image">
@@ -67,12 +75,11 @@ if (image) {
67
75
  )
68
76
  }
69
77
 
70
- <h1 id={PAGE_TITLE_ID} data-page-title>{title}</h1>
78
+ <h1 id={PAGE_TITLE_ID} data-page-title set:html={title} />
71
79
  </div>
72
80
 
73
81
  <div class="hero-content-container">
74
- {tagline && <div class="tagline">{tagline}</div>}
75
-
82
+ {tagline && <div class="tagline" set:html={tagline} />}
76
83
  {
77
84
  actions.length > 0 && (
78
85
  <div class="actions">
@@ -91,7 +98,8 @@ if (image) {
91
98
  {...attrs}
92
99
  >
93
100
  {text}
94
- {icon?.html && <Fragment set:html={icon.html} />}
101
+ {icon?.type === 'icon' && <Icon name={icon.name} />}
102
+ {icon?.type === 'raw' && <Fragment set:html={icon.html} />}
95
103
  </LinkButton>
96
104
  )
97
105
  )}
@@ -1,14 +1,30 @@
1
1
  ---
2
- import userConfig from 'virtual:lucode-starlight-config';
2
+ import { AstroError } from 'astro/errors';
3
+ import { marked } from 'marked';
4
+ import userConfig from 'virtual:grove-starlight-config';
5
+ import starlightConfig from 'virtual:starlight/user-config';
6
+ import { createLocaleLookup, resolveLocalizedString } from '../../core/i18n';
3
7
  import Drawer from './parts/Drawer.astro';
4
8
 
5
- const { hasSidebar } = Astro.locals.starlightRoute;
6
-
7
- // `footerText` is a plain string from the consumer's `astro.config.mjs`.
8
- // Astro auto-escapes interpolated text, so emitting it directly is safe
9
- // and avoids the XSS sink that `marked.parseInline` + `set:html` would
10
- // create. Consumers who want rich formatting should render Markdown in
11
- // their own component instead of leaning on this slot.
9
+ const { hasSidebar, lang, locale } = Astro.locals.starlightRoute;
10
+
11
+ const localeKeys = createLocaleLookup({
12
+ lang,
13
+ locale: Astro.currentLocale ?? locale,
14
+ defaultLang: starlightConfig.defaultLocale?.lang,
15
+ defaultLocale: starlightConfig.defaultLocale?.locale,
16
+ });
17
+
18
+ let footerText: string;
19
+
20
+ try {
21
+ footerText = resolveLocalizedString(userConfig.footerText, localeKeys);
22
+ } catch (error) {
23
+ throw new AstroError(
24
+ error instanceof Error ? error.message : 'Invalid footerText localization.',
25
+ 'Update the Grove config so footerText includes a key for the default language.'
26
+ );
27
+ }
12
28
  ---
13
29
 
14
30
  <div data-slot="layout">
@@ -39,7 +55,7 @@ const { hasSidebar } = Astro.locals.starlightRoute;
39
55
  </main>
40
56
 
41
57
  <footer>
42
- <div data-slot="footer-text">{userConfig.footerText}</div>
58
+ <div data-slot="footer-text" set:html={marked.parseInline(footerText)} />
43
59
  </footer>
44
60
  </div>
45
61
 
@@ -16,5 +16,6 @@ import TableOfContents from './TableOfContents.astro';
16
16
  max-height: calc(100vh - var(--header-height) - 1px);
17
17
  top: calc(var(--header-height) + 1px);
18
18
  position: sticky;
19
+ overflow-y: auto;
19
20
  }
20
21
  </style>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import { Icon } from '@astrojs/starlight/components';
3
3
  import { PAGE_TITLE_ID } from '../../core/config/constants';
4
- import userConfig from 'virtual:lucode-starlight-config';
4
+ import userConfig from 'virtual:grove-starlight-config';
5
5
  import { Dropdown, DropdownContent, DropdownItem, DropdownTrigger } from '../custom/dropdown';
6
6
  import LinkButton from '../custom/LinkButton.astro';
7
7
 
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro';
2
3
  import SidebarSublist from './parts/SidebarSublist.astro';
3
4
 
4
5
  const { sidebar } = Astro.locals.starlightRoute;
@@ -10,12 +11,48 @@ const { sidebar } = Astro.locals.starlightRoute;
10
11
  <div class="sidebar-fade-top"></div>
11
12
  <div class="container-sidebar">
12
13
  <div class="sidebar-entries">
13
- <SidebarSublist sublist={sidebar} />
14
+ {/* Keeps collapsed groups open and the scroll position across navigations. */}
15
+ <SidebarPersister>
16
+ <SidebarSublist sublist={sidebar} />
17
+ </SidebarPersister>
14
18
  </div>
15
19
  <div class="sidebar-fade-bottom"></div>
16
20
  </div>
17
21
  </div>
18
22
 
23
+ {/*
24
+ Bring the current page's entry into view inside the sidebar's own scroll
25
+ container. SidebarPersister keeps the scroll position across navigations, but
26
+ on a fresh load (or the first visit to a deep page) the sidebar starts at the
27
+ top, so an entry far down the list renders off-screen with no hint that it is
28
+ selected. Only the container is scrolled -- never the page -- and only when
29
+ the entry is actually outside the visible band.
30
+ */}
31
+ <script is:inline data-astro-rerun>
32
+ (() => {
33
+ const reveal = () => {
34
+ const container = document.querySelector('.container-sidebar');
35
+ const current = container?.querySelector('[aria-current="page"]');
36
+ if (!container || !current) return;
37
+
38
+ const view = container.getBoundingClientRect();
39
+ const entry = current.getBoundingClientRect();
40
+ if (entry.top >= view.top && entry.bottom <= view.bottom) return;
41
+
42
+ const offset =
43
+ entry.top - view.top - container.clientHeight / 2 + entry.height / 2;
44
+ // Instant, not smooth: this positions the sidebar for the page
45
+ // being loaded, so it should already be correct on first paint
46
+ // rather than animating into place while the reader looks at it.
47
+ container.scrollTop += offset;
48
+ };
49
+
50
+ // Runs after SidebarPersister has restored any stored scroll position.
51
+ requestAnimationFrame(reveal);
52
+ document.addEventListener('astro:page-load', () => requestAnimationFrame(reveal));
53
+ })();
54
+ </script>
55
+
19
56
  <style>
20
57
  .sidebar {
21
58
  scrollbar-width: none;
@@ -32,7 +32,9 @@ const links = config.social || [];
32
32
  cursor: pointer;
33
33
  color: color-mix(in oklab, var(--foreground) 76%, var(--muted-foreground));
34
34
  background-color: transparent;
35
- transition: color 0.15s, background-color 0.15s;
35
+ transition:
36
+ color 0.15s,
37
+ background-color 0.15s;
36
38
  text-decoration: none;
37
39
  }
38
40
  a:hover {
@@ -2,7 +2,7 @@
2
2
 
3
3
  ---
4
4
 
5
- <starlight-theme-lucode-select>
5
+ <starlight-theme-grove-select>
6
6
  <button
7
7
  aria-label={Astro.locals.t('themeSelect.accessibleLabel')}
8
8
  aria-live="polite"
@@ -49,7 +49,7 @@
49
49
 
50
50
  <span class="sr-only">Toggle theme</span>
51
51
  </button>
52
- </starlight-theme-lucode-select>
52
+ </starlight-theme-grove-select>
53
53
 
54
54
  <style>
55
55
  button {
@@ -137,8 +137,8 @@
137
137
  });
138
138
 
139
139
  customElements.define(
140
- 'starlight-theme-lucode-select',
141
- class StarlightThemeLucodeSelect extends HTMLElement {
140
+ 'starlight-theme-grove-select',
141
+ class StarlightThemeGroveSelect extends HTMLElement {
142
142
  constructor() {
143
143
  super();
144
144
  onThemeChange(loadTheme());
@@ -42,7 +42,7 @@ const { toc } = Astro.locals.starlightRoute;
42
42
  }
43
43
 
44
44
  .main {
45
- max-width: 40rem;
45
+ max-width: var(--content-max-width);
46
46
  min-width: 0;
47
47
  width: 100%;
48
48
  margin-inline: auto;
@@ -1,10 +1,19 @@
1
1
  ---
2
- import userConfig from 'virtual:lucode-starlight-config';
3
- import { getTranslation } from './NavBar.astro';
2
+ import userConfig from 'virtual:grove-starlight-config';
3
+ import starlightConfig from 'virtual:starlight/user-config';
4
4
  import { getRelativeLocaleUrl } from 'astro:i18n';
5
- const { sidebar } = Astro.locals.starlightRoute;
5
+ import { createLocaleLookup, resolveNavLabel } from '../../../core/i18n';
6
+
7
+ const { sidebar, lang, locale } = Astro.locals.starlightRoute;
6
8
 
7
9
  const currentPath = Astro.url.pathname;
10
+
11
+ const localeKeys = createLocaleLookup({
12
+ lang,
13
+ locale: Astro.currentLocale ?? locale,
14
+ defaultLang: starlightConfig.defaultLocale?.lang,
15
+ defaultLocale: starlightConfig.defaultLocale?.locale,
16
+ });
8
17
  ---
9
18
 
10
19
  <div popover="auto" id="drawer">
@@ -25,10 +34,7 @@ const currentPath = Astro.url.pathname;
25
34
  !absoluteLinkRegex.test(nav.link) && Astro.currentLocale
26
35
  ? getRelativeLocaleUrl(Astro.currentLocale, nav.link)
27
36
  : nav.link;
28
- const label =
29
- typeof nav.label === 'string'
30
- ? nav.label
31
- : getTranslation(nav.label, nav.link, 'label');
37
+ const label = resolveNavLabel(nav.label, nav.translations, localeKeys);
32
38
 
33
39
  return (
34
40
  <a href={link} {...nav.attrs}>
@@ -1,36 +1,19 @@
1
1
  ---
2
- import { AstroError } from 'astro/errors';
3
- import userConfig from 'virtual:lucode-starlight-config';
2
+ import userConfig from 'virtual:grove-starlight-config';
4
3
  import starlightConfig from 'virtual:starlight/user-config';
5
4
  import { getRelativeLocaleUrl } from 'astro:i18n';
5
+ import { createLocaleLookup, resolveNavLabel } from '../../../core/i18n';
6
6
 
7
7
  const currentPath = Astro.url.pathname;
8
8
 
9
- const defaultLang =
10
- starlightConfig.defaultLocale?.lang || starlightConfig.defaultLocale?.locale || 'en';
9
+ const { lang, locale } = Astro.locals.starlightRoute;
11
10
 
12
- export function getTranslation(
13
- translations: Record<string, string>,
14
- link: string,
15
- description: string
16
- ) {
17
- const defaultTranslation = translations[defaultLang];
18
-
19
- if (!defaultTranslation) {
20
- throw new AstroError(
21
- `The ${description} for "${link}" must have a key for the default language "${defaultLang}".`,
22
- 'Update the Starlight config to include a topic label for the default language.'
23
- );
24
- }
25
-
26
- let translation = defaultTranslation;
27
-
28
- if (Astro.currentLocale) {
29
- translation = translations[Astro.currentLocale] ?? defaultTranslation;
30
- }
31
-
32
- return translation;
33
- }
11
+ const localeKeys = createLocaleLookup({
12
+ lang,
13
+ locale: Astro.currentLocale ?? locale,
14
+ defaultLang: starlightConfig.defaultLocale?.lang,
15
+ defaultLocale: starlightConfig.defaultLocale?.locale,
16
+ });
34
17
  ---
35
18
 
36
19
  <nav class="nav-bar">
@@ -42,10 +25,8 @@ export function getTranslation(
42
25
  !absoluteLinkRegex.test(nav.link) && Astro.currentLocale
43
26
  ? getRelativeLocaleUrl(Astro.currentLocale, nav.link)
44
27
  : nav.link;
45
- const label =
46
- typeof nav.label === 'string'
47
- ? nav.label
48
- : getTranslation(nav.label, nav.link, 'label');
28
+
29
+ const label = resolveNavLabel(nav.label, nav.translations, localeKeys);
49
30
 
50
31
  return (
51
32
  <a class:list={[{ active: currentPath === link }]} href={link} {...nav.attrs}>
@@ -1,12 +1,15 @@
1
1
  ---
2
+ import { Icon } from '@astrojs/starlight/components';
3
+ import SidebarRestorePoint from '@astrojs/starlight/components/SidebarRestorePoint.astro';
2
4
  import type { StarlightRouteData } from '@astrojs/starlight/route-data';
5
+ import { isSidebarGroupOpen } from '../../../core/sidebar';
3
6
 
4
7
  interface Props {
5
8
  sublist: StarlightRouteData['sidebar'];
6
9
  nested?: boolean;
7
10
  }
8
11
 
9
- const { sublist } = Astro.props;
12
+ const { sublist, nested = false } = Astro.props;
10
13
  ---
11
14
 
12
15
  {
@@ -14,7 +17,7 @@ const { sublist } = Astro.props;
14
17
  entry.type === 'link' ? (
15
18
  <a
16
19
  href={entry.href}
17
- aria-current={entry.isCurrent && 'page'}
20
+ aria-current={entry.isCurrent ? 'page' : undefined}
18
21
  class:list={['entry-link', entry.attrs.class]}
19
22
  {...entry.attrs}
20
23
  >
@@ -23,6 +26,20 @@ const { sublist } = Astro.props;
23
26
  {entry.badge && <span class="entry-badge">{entry.badge.text}</span>}
24
27
  </span>
25
28
  </a>
29
+ ) : nested ? (
30
+ <details class="entry-group" open={isSidebarGroupOpen(entry)}>
31
+ <summary class="entry-group-summary">
32
+ <span class="entry-link-inner">
33
+ {entry.label}
34
+ {entry.badge && <span class="entry-badge">{entry.badge.text}</span>}
35
+ </span>
36
+ <Icon name="right-caret" class="entry-group-caret" size="1rem" />
37
+ </summary>
38
+ <SidebarRestorePoint />
39
+ <div class="container-group-link nested">
40
+ <Astro.self sublist={entry.entries} nested />
41
+ </div>
42
+ </details>
26
43
  ) : (
27
44
  <div class="container-sidebar-entry">
28
45
  <h4 class="entry-title">{entry.label}</h4>
@@ -62,6 +79,60 @@ const { sublist } = Astro.props;
62
79
  display: grid;
63
80
  }
64
81
 
82
+ /*
83
+ * Nested levels are indented with a guide line rather than plain padding, so that a reader can
84
+ * follow which group a link belongs to once the tree is more than one level deep.
85
+ */
86
+ .container-group-link.nested {
87
+ margin-left: calc(var(--spacing) * 2);
88
+ padding-left: calc(var(--spacing) * 2);
89
+ border-left: 1px solid var(--border);
90
+ }
91
+
92
+ .entry-group {
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 2px;
96
+ }
97
+
98
+ .entry-group-summary {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ gap: calc(var(--spacing) * 1);
103
+ height: 1.875rem;
104
+ cursor: pointer;
105
+ list-style: none;
106
+ color: var(--foreground);
107
+ font-size: 0.8rem;
108
+ line-height: 1.125rem;
109
+ border-radius: calc(var(--radius) - 2px);
110
+ transition:
111
+ color 0.15s,
112
+ background-color 0.15s;
113
+ }
114
+
115
+ /* Safari still paints its own marker without this. */
116
+ .entry-group-summary::-webkit-details-marker {
117
+ display: none;
118
+ }
119
+
120
+ .entry-group-summary:hover {
121
+ background-color: var(--secondary);
122
+ color: var(--secondary-foreground);
123
+ }
124
+
125
+ .entry-group-caret {
126
+ flex-shrink: 0;
127
+ margin-right: calc(var(--spacing) * 2);
128
+ color: var(--muted-foreground);
129
+ transition: transform 0.15s;
130
+ }
131
+
132
+ .entry-group[open] > .entry-group-summary .entry-group-caret {
133
+ transform: rotate(90deg);
134
+ }
135
+
65
136
  .entry-link {
66
137
  color: var(--foreground);
67
138
  font-weight: 400;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Starlight validates frontmatter with `z.object`, which strips keys it does not know about. When
3
+ * `docsSchema()` is called without `extend: ExtendDocsSchema`, the theme's own `hero` fields are
4
+ * therefore dropped without any error, and splash pages silently fall back to the default layout.
5
+ *
6
+ * Detecting that from the parsed frontmatter is unreliable: Starlight builds some entries in code
7
+ * rather than through the schema — its fallback 404 route hardcodes `hero` — so those look stripped
8
+ * even on a correctly configured site. Instead, `@grove-dev/starlight/schema` records that it was
9
+ * imported, which answers the actual question: did the user wire the extension up at all?
10
+ */
11
+ const LOADED = Symbol.for('@grove-dev/starlight.docs-schema-loaded');
12
+
13
+ /** Called by `@grove-dev/starlight/schema` on import. */
14
+ export function markDocsSchemaLoaded(): void {
15
+ (globalThis as Record<symbol, unknown>)[LOADED] = true;
16
+ }
17
+
18
+ export function isDocsSchemaExtended(): boolean {
19
+ return (globalThis as Record<symbol, unknown>)[LOADED] === true;
20
+ }
21
+
22
+ let warned = false;
23
+
24
+ /**
25
+ * Emits {@link missingDocsSchemaWarning} the first time a page with a hero is rendered on a site
26
+ * that never imported the schema. Returns whether it warned; later calls are no-ops, so a site with
27
+ * several splash pages does not repeat the same advice once per page.
28
+ */
29
+ export function warnAboutMissingDocsSchemaOnce(
30
+ hero: unknown,
31
+ warn: (message: string) => void = console.warn
32
+ ): boolean {
33
+ if (warned || hero == null || isDocsSchemaExtended()) return false;
34
+
35
+ warned = true;
36
+ warn(missingDocsSchemaWarning());
37
+ return true;
38
+ }
39
+
40
+ /** Test seam: clears the once-per-process guard used by {@link warnAboutMissingDocsSchemaOnce}. */
41
+ export function resetDocsSchemaWarning(): void {
42
+ warned = false;
43
+ }
44
+
45
+ /** The advice printed when the schema extension is missing. */
46
+ export function missingDocsSchemaWarning(): string {
47
+ return [
48
+ '[@grove-dev/starlight] This page sets `hero` frontmatter, but the docs schema is not extended,',
49
+ "so Starlight is dropping the theme's hero fields. `hero.layout`, `hero.announcement` and the",
50
+ 'extra `hero.actions[].variant` values have no effect until you extend it:',
51
+ '',
52
+ " import { ExtendDocsSchema } from '@grove-dev/starlight/schema';",
53
+ '',
54
+ ' schema: docsSchema({ extend: ExtendDocsSchema }),',
55
+ '',
56
+ 'See https://withgrove.dev/reference/plugin-api/#frontmatter-extension',
57
+ ].join('\n');
58
+ }
@@ -1,5 +1,6 @@
1
1
  import type { HookParameters } from '@astrojs/starlight/types';
2
2
  import type { AstroIntegrationLogger } from 'astro';
3
+ import type { GroveStarlightConfig } from './schemas';
3
4
 
4
5
  type StarlightUserConfig = HookParameters<'config:setup'>['config'];
5
6
  type ComponentOverride = keyof NonNullable<StarlightUserConfig['components']>;
@@ -25,6 +26,7 @@ export const COMPONENT_OVERRIDES: ComponentOverride[] = [
25
26
 
26
27
  export function override(
27
28
  starlightConfig: StarlightUserConfig,
29
+ pluginConfig: GroveStarlightConfig,
28
30
  overrides: ComponentOverride[],
29
31
  logger: AstroIntegrationLogger
30
32
  ): StarlightUserConfig['components'] {
@@ -33,12 +35,14 @@ export function override(
33
35
  if (starlightConfig.components?.[override] != null) {
34
36
  const fallback = `@grove-dev/starlight/components/overrides/${override}.astro`;
35
37
 
36
- logger.warn(
37
- `A \`<${override}>\` component override is already defined in your Starlight configuration.`
38
- );
39
- logger.warn(
40
- `To use \`@grove-dev/starlight/components\`, either remove this override or manually render the content from \`${fallback}\`.`
41
- );
38
+ if (pluginConfig.warnOverrides) {
39
+ logger.warn(
40
+ `A \`<${override}>\` component override is already defined in your Starlight configuration.`
41
+ );
42
+ logger.warn(
43
+ `To use \`@grove-dev/starlight/components\`, either remove this override or manually render the content from \`${fallback}\`.`
44
+ );
45
+ }
42
46
  continue;
43
47
  }
44
48
  components[override] = `@grove-dev/starlight/components/overrides/${override}.astro`;
@@ -18,12 +18,21 @@ export const linkSchema = z.object({
18
18
  */
19
19
  badge: z.string().optional(),
20
20
  /**
21
- * The topic label visible at the top of the sidebar.
21
+ * The link label.
22
22
  *
23
- * The value can be a string, or for multilingual sites, an object with values for each different locale. When using
24
- * the object form, the keys must be BCP-47 tags (e.g. en, fr, or zh-CN).
23
+ * - A string used as the default-locale label (pair with `translations` for other languages).
24
+ * - Or a locale map keyed by BCP-47 tags / locale paths (e.g. `en`, `es`).
25
+ *
26
+ * @see https://starlight.astro.build/guides/sidebar/#internationalization
25
27
  */
26
28
  label: z.union([z.string(), z.record(z.string(), z.string())]),
29
+ /**
30
+ * Optional labels for other languages when `label` is a string.
31
+ * Keys should be BCP-47 tags (e.g. `en`, `es`), matching Starlight sidebar translations.
32
+ *
33
+ * @see https://starlight.astro.build/guides/sidebar/#internationalization
34
+ */
35
+ translations: z.record(z.string(), z.string()).optional(),
27
36
  /**
28
37
  * The link to the topic’s content which an be a relative link to local files or the full URL of an external page.
29
38
  *
@@ -37,7 +46,8 @@ export const linkSchema = z.object({
37
46
 
38
47
  export type Link = z.infer<typeof linkSchema>;
39
48
 
40
- export const LucodeStarlightConfigSchema = z.object({
49
+ export const GroveStarlightConfigSchema = z.object({
50
+ /** Array of navigation links for the header/nav bar. */
41
51
  navLinks: z.array(linkSchema).optional(),
42
52
  docs: z
43
53
  .object({
@@ -45,13 +55,24 @@ export const LucodeStarlightConfigSchema = z.object({
45
55
  })
46
56
  .optional()
47
57
  .default({ includeAiUtilities: false }),
58
+ /**
59
+ * Whether to warn when a component override defined in your Starlight configuration prevents
60
+ * the theme from applying its own. Set to `false` to silence those warnings.
61
+ */
62
+ warnOverrides: z.boolean().optional().default(true),
63
+ /**
64
+ * Footer Markdown text. Can be a string, or for multilingual sites an object with values for
65
+ * each locale. Keys may be BCP-47 tags (e.g. `en`, `es`) or locale paths.
66
+ *
67
+ * @see https://starlight.astro.build/reference/configuration/#title
68
+ */
48
69
  footerText: z
49
- .string()
70
+ .union([z.string(), z.record(z.string(), z.string())])
50
71
  .optional()
51
72
  .default(
52
- 'Inspired by the [shadcn/ui](https://ui.shadcn.com/) documentation theme and based on [starlight-theme-black](https://github.com/adrian-ub/starlight-theme-black). Ported to Astro Starlight by [lucas-labs](https://github.com/lucas-labs).'
73
+ 'Inspired by the [shadcn/ui](https://ui.shadcn.com/) documentation theme and based on [starlight-theme-black](https://github.com/adrian-ub/starlight-theme-black). Originally forked from [lucas-labs/lucode-starlight-theme](https://github.com/lucas-labs/lucode-starlight-theme) and maintained by [grove](https://github.com/tortuvshin/grove).'
53
74
  ),
54
75
  });
55
76
 
56
- export type LucodeStarlightUserConfig = z.input<typeof LucodeStarlightConfigSchema>;
57
- export type LucodeStarlightConfig = z.output<typeof LucodeStarlightConfigSchema>;
77
+ export type GroveStarlightUserConfig = z.input<typeof GroveStarlightConfigSchema>;
78
+ export type GroveStarlightConfig = z.output<typeof GroveStarlightConfigSchema>;
@@ -1,13 +1,13 @@
1
1
  import type { ViteUserConfig } from 'astro';
2
- import type { LucodeStarlightConfig } from './schemas';
2
+ import type { GroveStarlightConfig } from './schemas';
3
3
 
4
- export function vitePlugin(config: LucodeStarlightConfig): VitePlugin {
5
- const moduleId = 'virtual:lucode-starlight-config';
4
+ export function vitePlugin(config: GroveStarlightConfig): VitePlugin {
5
+ const moduleId = 'virtual:grove-starlight-config';
6
6
  const resolvedModuleId = `\0${moduleId}`;
7
7
  const moduleContent = `export default ${JSON.stringify(config)}`;
8
8
 
9
9
  return {
10
- name: 'vite-plugin-lucode-starlight',
10
+ name: 'vite-plugin-grove-starlight',
11
11
  load(id) {
12
12
  return id === resolvedModuleId ? moduleContent : undefined;
13
13
  },