@grove-dev/starlight 0.7.0 → 0.9.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.
Files changed (34) hide show
  1. package/components/custom/ContainerSection.astro +1 -1
  2. package/components/custom/LinkButton.astro +7 -7
  3. package/components/custom/dropdown/Dropdown.astro +5 -5
  4. package/components/custom/dropdown/DropdownContent.astro +26 -26
  5. package/components/custom/dropdown/DropdownItem.astro +8 -8
  6. package/components/custom/dropdown/DropdownLabel.astro +4 -4
  7. package/components/custom/dropdown/DropdownTrigger.astro +12 -12
  8. package/components/custom/dropdown/index.ts +14 -14
  9. package/components/overrides/Footer.astro +3 -3
  10. package/components/overrides/Header.astro +5 -6
  11. package/components/overrides/Hero.astro +17 -17
  12. package/components/overrides/PageFrame.astro +11 -11
  13. package/components/overrides/PageTitle.astro +3 -3
  14. package/components/overrides/Search.astro +6 -6
  15. package/components/overrides/parts/Drawer.astro +5 -5
  16. package/components/overrides/parts/NavBar.astro +5 -5
  17. package/components/overrides/parts/SidebarSublist.astro +2 -2
  18. package/components/overrides/parts/toc/TableOfContentsList.astro +4 -4
  19. package/components/overrides/parts/toc/starlight-toc.ts +96 -98
  20. package/core/config/docs-schema.ts +20 -20
  21. package/core/config/expresive-code.ts +49 -51
  22. package/core/config/override.ts +36 -36
  23. package/core/config/schemas.ts +59 -59
  24. package/core/config/vite.ts +12 -12
  25. package/core/i18n.ts +113 -113
  26. package/core/plugin.ts +42 -42
  27. package/core/sidebar.ts +4 -4
  28. package/global.d.ts +3 -3
  29. package/package.json +1 -1
  30. package/schema.ts +39 -39
  31. package/styles/base.css +866 -907
  32. package/styles/theme.css +61 -61
  33. package/user-components.ts +1 -1
  34. package/virtual.d.ts +27 -27
@@ -2,19 +2,19 @@ import type { ViteUserConfig } from 'astro';
2
2
  import type { GroveStarlightConfig } from './schemas';
3
3
 
4
4
  export function vitePlugin(config: GroveStarlightConfig): VitePlugin {
5
- const moduleId = 'virtual:grove-starlight-config';
6
- const resolvedModuleId = `\0${moduleId}`;
7
- const moduleContent = `export default ${JSON.stringify(config)}`;
5
+ const moduleId = 'virtual:grove-starlight-config';
6
+ const resolvedModuleId = `\0${moduleId}`;
7
+ const moduleContent = `export default ${JSON.stringify(config)}`;
8
8
 
9
- return {
10
- name: 'vite-plugin-grove-starlight',
11
- load(id) {
12
- return id === resolvedModuleId ? moduleContent : undefined;
13
- },
14
- resolveId(id) {
15
- return id === moduleId ? resolvedModuleId : undefined;
16
- },
17
- };
9
+ return {
10
+ name: 'vite-plugin-grove-starlight',
11
+ load(id) {
12
+ return id === resolvedModuleId ? moduleContent : undefined;
13
+ },
14
+ resolveId(id) {
15
+ return id === moduleId ? resolvedModuleId : undefined;
16
+ },
17
+ };
18
18
  }
19
19
 
20
20
  type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
package/core/i18n.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  export type LocaleLookup = {
2
- /** BCP-47 tag from the active locale (`locales[x].lang`), e.g. `en`. */
3
- lang?: string;
4
- /** Locale path key (`Astro.currentLocale`), e.g. `en`. */
5
- locale?: string;
6
- /** Default locale BCP-47 tag or path used as fallback. */
7
- defaultLang: string;
8
- /** Default locale path key, e.g. `en`. */
9
- defaultLocale?: string;
2
+ /** BCP-47 tag from the active locale (`locales[x].lang`), e.g. `en`. */
3
+ lang?: string;
4
+ /** Locale path key (`Astro.currentLocale`), e.g. `en`. */
5
+ locale?: string;
6
+ /** Default locale BCP-47 tag or path used as fallback. */
7
+ defaultLang: string;
8
+ /** Default locale path key, e.g. `en`. */
9
+ defaultLocale?: string;
10
10
  };
11
11
 
12
12
  /**
@@ -18,63 +18,63 @@ export type LocaleLookup = {
18
18
  * (`es-es`), without letting the default locale shadow an active case-insensitive hit.
19
19
  */
20
20
  export function pickLocalized(
21
- dictionary: Record<string, string> | undefined,
22
- candidates: Array<string | undefined>,
23
- fallbacks: Array<string | undefined> = []
21
+ dictionary: Record<string, string> | undefined,
22
+ candidates: Array<string | undefined>,
23
+ fallbacks: Array<string | undefined> = [],
24
24
  ): string | undefined {
25
- if (!dictionary) {
26
- return undefined;
25
+ if (!dictionary) {
26
+ return undefined;
27
+ }
28
+
29
+ const lowerMap = Object.fromEntries(
30
+ Object.entries(dictionary).map(([key, value]) => [key.toLowerCase(), value]),
31
+ );
32
+
33
+ const pickExact = (keys: Array<string | undefined>) => {
34
+ for (const key of keys) {
35
+ if (key && dictionary[key]) {
36
+ return dictionary[key];
37
+ }
27
38
  }
28
-
29
- const lowerMap = Object.fromEntries(
30
- Object.entries(dictionary).map(([key, value]) => [key.toLowerCase(), value])
31
- );
32
-
33
- const pickExact = (keys: Array<string | undefined>) => {
34
- for (const key of keys) {
35
- if (key && dictionary[key]) {
36
- return dictionary[key];
37
- }
38
- }
39
- return undefined;
40
- };
41
-
42
- const pickCaseInsensitive = (keys: Array<string | undefined>) => {
43
- for (const key of keys) {
44
- if (!key) {
45
- continue;
46
- }
47
-
48
- const match = lowerMap[key.toLowerCase()];
49
- if (match) {
50
- return match;
51
- }
52
- }
53
- return undefined;
54
- };
55
-
56
- return (
57
- pickExact(candidates) ??
58
- pickCaseInsensitive(candidates) ??
59
- pickExact(fallbacks) ??
60
- pickCaseInsensitive(fallbacks)
61
- );
39
+ return undefined;
40
+ };
41
+
42
+ const pickCaseInsensitive = (keys: Array<string | undefined>) => {
43
+ for (const key of keys) {
44
+ if (!key) {
45
+ continue;
46
+ }
47
+
48
+ const match = lowerMap[key.toLowerCase()];
49
+ if (match) {
50
+ return match;
51
+ }
52
+ }
53
+ return undefined;
54
+ };
55
+
56
+ return (
57
+ pickExact(candidates) ??
58
+ pickCaseInsensitive(candidates) ??
59
+ pickExact(fallbacks) ??
60
+ pickCaseInsensitive(fallbacks)
61
+ );
62
62
  }
63
63
 
64
64
  /** @deprecated Prefer `pickLocalized`. Kept for existing call sites/tests. */
65
65
  export function pickLang(
66
- dictionary: Record<string, string> | undefined,
67
- lang: string
66
+ dictionary: Record<string, string> | undefined,
67
+ lang: string,
68
68
  ): string | undefined {
69
- return pickLocalized(dictionary, [lang]);
69
+ return pickLocalized(dictionary, [lang]);
70
70
  }
71
71
 
72
72
  function activeKeys({ lang, locale }: LocaleLookup): Array<string | undefined> {
73
- return [lang, locale];
73
+ return [lang, locale];
74
74
  }
75
75
 
76
76
  function fallbackKeys({ defaultLang, defaultLocale }: LocaleLookup): Array<string | undefined> {
77
- return [defaultLang, defaultLocale];
77
+ return [defaultLang, defaultLocale];
78
78
  }
79
79
 
80
80
  /**
@@ -85,25 +85,25 @@ function fallbackKeys({ defaultLang, defaultLocale }: LocaleLookup): Array<strin
85
85
  * - Locale map style: `label: Record<BCP-47 | locale-path, string>`
86
86
  */
87
87
  export function resolveNavLabel(
88
- label: string | Record<string, string>,
89
- translations: Record<string, string> | undefined,
90
- keys: LocaleLookup
88
+ label: string | Record<string, string>,
89
+ translations: Record<string, string> | undefined,
90
+ keys: LocaleLookup,
91
91
  ): string {
92
- const primary = activeKeys(keys);
93
- const fallback = fallbackKeys(keys);
92
+ const primary = activeKeys(keys);
93
+ const fallback = fallbackKeys(keys);
94
94
 
95
- if (typeof label === 'string') {
96
- return pickLocalized(translations, primary, fallback) || label;
97
- }
95
+ if (typeof label === 'string') {
96
+ return pickLocalized(translations, primary, fallback) || label;
97
+ }
98
98
 
99
- const resolved = pickLocalized(label, primary, fallback);
100
- if (resolved) {
101
- return resolved;
102
- }
99
+ const resolved = pickLocalized(label, primary, fallback);
100
+ if (resolved) {
101
+ return resolved;
102
+ }
103
103
 
104
- throw new Error(
105
- `Localized label must include a key for the default language "${keys.defaultLang}".`
106
- );
104
+ throw new Error(
105
+ `Localized label must include a key for the default language "${keys.defaultLang}".`,
106
+ );
107
107
  }
108
108
 
109
109
  /**
@@ -111,15 +111,15 @@ export function resolveNavLabel(
111
111
  * Accepts a LocaleLookup or a bare lang string for convenience.
112
112
  */
113
113
  export function resolveLabel(
114
- label: string,
115
- translations: Record<string, string> | undefined,
116
- langOrKeys: string | LocaleLookup
114
+ label: string,
115
+ translations: Record<string, string> | undefined,
116
+ langOrKeys: string | LocaleLookup,
117
117
  ): string {
118
- if (typeof langOrKeys === 'string') {
119
- return pickLocalized(translations, [langOrKeys]) || label;
120
- }
118
+ if (typeof langOrKeys === 'string') {
119
+ return pickLocalized(translations, [langOrKeys]) || label;
120
+ }
121
121
 
122
- return resolveNavLabel(label, translations, langOrKeys);
122
+ return resolveNavLabel(label, translations, langOrKeys);
123
123
  }
124
124
 
125
125
  /**
@@ -127,49 +127,49 @@ export function resolveLabel(
127
127
  * Prefers the active language/locale, then falls back to the default language.
128
128
  */
129
129
  export function resolveLocalizedString(
130
- value: string | Record<string, string>,
131
- langOrKeys: string | LocaleLookup,
132
- defaultLang?: string
130
+ value: string | Record<string, string>,
131
+ langOrKeys: string | LocaleLookup,
132
+ defaultLang?: string,
133
133
  ): string {
134
- if (typeof value === 'string') {
135
- return value;
136
- }
137
-
138
- const keys: LocaleLookup =
139
- typeof langOrKeys === 'string'
140
- ? { lang: langOrKeys, defaultLang: defaultLang ?? langOrKeys }
141
- : langOrKeys;
142
-
143
- const resolved = pickLocalized(value, activeKeys(keys), fallbackKeys(keys));
144
- if (resolved) {
145
- return resolved;
146
- }
147
-
148
- throw new Error(
149
- `Localized string must include a key for the default language "${keys.defaultLang}".`
150
- );
134
+ if (typeof value === 'string') {
135
+ return value;
136
+ }
137
+
138
+ const keys: LocaleLookup =
139
+ typeof langOrKeys === 'string'
140
+ ? { lang: langOrKeys, defaultLang: defaultLang ?? langOrKeys }
141
+ : langOrKeys;
142
+
143
+ const resolved = pickLocalized(value, activeKeys(keys), fallbackKeys(keys));
144
+ if (resolved) {
145
+ return resolved;
146
+ }
147
+
148
+ throw new Error(
149
+ `Localized string must include a key for the default language "${keys.defaultLang}".`,
150
+ );
151
151
  }
152
152
 
153
153
  /** Build a LocaleLookup from Starlight + Astro locale values. */
154
154
  export function createLocaleLookup(options: {
155
- lang?: string;
156
- locale?: string;
157
- defaultLang?: string;
158
- defaultLocale?: string;
155
+ lang?: string;
156
+ locale?: string;
157
+ defaultLang?: string;
158
+ defaultLocale?: string;
159
159
  }): LocaleLookup {
160
- const result: LocaleLookup = {
161
- defaultLang: options.defaultLang || options.defaultLocale || 'en',
162
- };
163
-
164
- if (options.lang !== undefined) {
165
- result.lang = options.lang;
166
- }
167
- if (options.locale !== undefined) {
168
- result.locale = options.locale;
169
- }
170
- if (options.defaultLocale !== undefined) {
171
- result.defaultLocale = options.defaultLocale;
172
- }
173
-
174
- return result;
160
+ const result: LocaleLookup = {
161
+ defaultLang: options.defaultLang || options.defaultLocale || 'en',
162
+ };
163
+
164
+ if (options.lang !== undefined) {
165
+ result.lang = options.lang;
166
+ }
167
+ if (options.locale !== undefined) {
168
+ result.locale = options.locale;
169
+ }
170
+ if (options.defaultLocale !== undefined) {
171
+ result.defaultLocale = options.defaultLocale;
172
+ }
173
+
174
+ return result;
175
175
  }
package/core/plugin.ts CHANGED
@@ -1,58 +1,58 @@
1
1
  import type { StarlightPlugin } from '@astrojs/starlight/types';
2
- import { override, COMPONENT_OVERRIDES } from './config/override';
3
2
  import { expressiveCode } from './config/expresive-code';
4
- import { vitePlugin } from './config/vite';
3
+ import { COMPONENT_OVERRIDES, override } from './config/override';
5
4
  import {
6
- GroveStarlightConfigSchema,
7
- type GroveStarlightConfig,
8
- type GroveStarlightUserConfig,
5
+ type GroveStarlightConfig,
6
+ GroveStarlightConfigSchema,
7
+ type GroveStarlightUserConfig,
9
8
  } from './config/schemas';
9
+ import { vitePlugin } from './config/vite';
10
10
 
11
11
  const parseConfig = (userConfig?: GroveStarlightUserConfig): GroveStarlightConfig => {
12
- const parsedConfig = GroveStarlightConfigSchema.safeParse(userConfig ?? {});
12
+ const parsedConfig = GroveStarlightConfigSchema.safeParse(userConfig ?? {});
13
13
 
14
- if (!parsedConfig.success) {
15
- throw new Error(
16
- `The provided plugin configuration for grove-starlight is invalid.\n${parsedConfig.error.issues.map((issue) => issue.message).join('\n')}`
17
- );
18
- }
14
+ if (!parsedConfig.success) {
15
+ throw new Error(
16
+ `The provided plugin configuration for grove-starlight is invalid.\n${parsedConfig.error.issues.map((issue) => issue.message).join('\n')}`,
17
+ );
18
+ }
19
19
 
20
- return parsedConfig.data;
20
+ return parsedConfig.data;
21
21
  };
22
22
 
23
23
  const plugin = (userConfig: GroveStarlightUserConfig = {}): StarlightPlugin =>
24
- ({
25
- name: 'grove-starlight',
26
- hooks: {
27
- 'config:setup': ({ config, logger, updateConfig, addIntegration }) => {
28
- const pluginConfig = parseConfig(userConfig);
24
+ ({
25
+ name: 'grove-starlight',
26
+ hooks: {
27
+ 'config:setup': ({ config, logger, updateConfig, addIntegration }) => {
28
+ const pluginConfig = parseConfig(userConfig);
29
29
 
30
- updateConfig({
31
- components: override(config, pluginConfig, COMPONENT_OVERRIDES, logger),
32
- customCss: [
33
- ...(config.customCss ?? []),
34
- '@grove-dev/starlight/styles/layers',
35
- '@grove-dev/starlight/styles/theme',
36
- '@grove-dev/starlight/styles/base',
37
- ],
38
- expressiveCode: expressiveCode(config),
39
- });
30
+ updateConfig({
31
+ components: override(config, pluginConfig, COMPONENT_OVERRIDES, logger),
32
+ customCss: [
33
+ ...(config.customCss ?? []),
34
+ '@grove-dev/starlight/styles/layers',
35
+ '@grove-dev/starlight/styles/theme',
36
+ '@grove-dev/starlight/styles/base',
37
+ ],
38
+ expressiveCode: expressiveCode(config),
39
+ });
40
40
 
41
- addIntegration({
42
- name: 'grove-starlight-integration',
43
- hooks: {
44
- 'astro:config:setup': ({ updateConfig }) => {
45
- updateConfig({
46
- vite: { plugins: [vitePlugin(pluginConfig)] },
47
- });
48
- },
49
- },
50
- });
41
+ addIntegration({
42
+ name: 'grove-starlight-integration',
43
+ hooks: {
44
+ 'astro:config:setup': ({ updateConfig }) => {
45
+ updateConfig({
46
+ vite: { plugins: [vitePlugin(pluginConfig)] },
47
+ });
51
48
  },
52
- // 'i18n:setup': function ({ injectTranslations }) {
53
- // injectTranslations(translations);
54
- // },
55
- },
56
- }) satisfies StarlightPlugin;
49
+ },
50
+ });
51
+ },
52
+ // 'i18n:setup': function ({ injectTranslations }) {
53
+ // injectTranslations(translations);
54
+ // },
55
+ },
56
+ }) satisfies StarlightPlugin;
57
57
 
58
58
  export { plugin };
package/core/sidebar.ts CHANGED
@@ -6,9 +6,9 @@ export type SidebarLink = Extract<SidebarEntry, { type: 'link' }>;
6
6
 
7
7
  /** Every link reachable from `entries`, at any depth. */
8
8
  export function flattenSidebar(entries: SidebarEntry[]): SidebarLink[] {
9
- return entries.flatMap((entry) =>
10
- entry.type === 'group' ? flattenSidebar(entry.entries) : [entry]
11
- );
9
+ return entries.flatMap((entry) =>
10
+ entry.type === 'group' ? flattenSidebar(entry.entries) : [entry],
11
+ );
12
12
  }
13
13
 
14
14
  /**
@@ -18,5 +18,5 @@ export function flattenSidebar(entries: SidebarEntry[]): SidebarLink[] {
18
18
  * the reader would land on a page with no idea where they are in the tree.
19
19
  */
20
20
  export function isSidebarGroupOpen(group: SidebarGroup): boolean {
21
- return !group.collapsed || flattenSidebar(group.entries).some((link) => link.isCurrent);
21
+ return !group.collapsed || flattenSidebar(group.entries).some((link) => link.isCurrent);
22
22
  }
package/global.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export declare global {
2
- var StarlightThemeProvider: {
3
- updatePickers(theme?: string): void;
4
- };
2
+ var StarlightThemeProvider: {
3
+ updatePickers(theme?: string): void;
4
+ };
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grove-dev/starlight",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Grove's theme for Starlight (the Astro native documentation site generator)",
6
6
  "author": "grove-dev",
package/schema.ts CHANGED
@@ -6,50 +6,50 @@ import { markDocsSchemaLoaded } from './core/config/docs-schema';
6
6
  markDocsSchemaLoaded();
7
7
 
8
8
  export const heroLayoutSchema = z
9
- .enum(['centered', 'centered-top', 'split-left', 'split-right', 'banner'])
10
- .default('centered')
11
- .describe(
12
- 'The layout of the hero section. "centered" places the image below the text, "centered-top" places it above, "split-left" places text left and image right, "split-right" places text right and image left.'
13
- );
9
+ .enum(['centered', 'centered-top', 'split-left', 'split-right', 'banner'])
10
+ .default('centered')
11
+ .describe(
12
+ 'The layout of the hero section. "centered" places the image below the text, "centered-top" places it above, "split-left" places text left and image right, "split-right" places text right and image left.',
13
+ );
14
14
 
15
15
  export type HeroLayout = z.infer<typeof heroLayoutSchema>;
16
16
 
17
17
  export const ExtendDocsSchema = z.object({
18
- hero: z
18
+ hero: z
19
+ .object({
20
+ layout: heroLayoutSchema,
21
+ announcement: z
19
22
  .object({
20
- layout: heroLayoutSchema,
21
- announcement: z
22
- .object({
23
- text: z.string(),
24
- link: z.string(),
25
- })
26
- .optional(),
27
- actions: z
28
- .array(
29
- z.object({
30
- /**
31
- * Button style to use. Starlight's own `primary` and `minimal` are accepted
32
- * as aliases of `default` and `ghost` so existing frontmatter keeps working.
33
- *
34
- * Requires `@astrojs/starlight >= 0.41.4`, which is the first version whose
35
- * `docsSchema({ extend })` deep-merges instead of intersecting — an
36
- * intersection cannot widen an enum Starlight already declares.
37
- */
38
- variant: z
39
- .enum([
40
- 'default',
41
- 'link',
42
- 'secondary',
43
- 'outline',
44
- 'ghost',
45
- 'destructive',
46
- 'primary',
47
- 'minimal',
48
- ])
49
- .default('default'),
50
- })
51
- )
52
- .default([]),
23
+ text: z.string(),
24
+ link: z.string(),
53
25
  })
54
26
  .optional(),
27
+ actions: z
28
+ .array(
29
+ z.object({
30
+ /**
31
+ * Button style to use. Starlight's own `primary` and `minimal` are accepted
32
+ * as aliases of `default` and `ghost` so existing frontmatter keeps working.
33
+ *
34
+ * Requires `@astrojs/starlight >= 0.41.4`, which is the first version whose
35
+ * `docsSchema({ extend })` deep-merges instead of intersecting — an
36
+ * intersection cannot widen an enum Starlight already declares.
37
+ */
38
+ variant: z
39
+ .enum([
40
+ 'default',
41
+ 'link',
42
+ 'secondary',
43
+ 'outline',
44
+ 'ghost',
45
+ 'destructive',
46
+ 'primary',
47
+ 'minimal',
48
+ ])
49
+ .default('default'),
50
+ }),
51
+ )
52
+ .default([]),
53
+ })
54
+ .optional(),
55
55
  });