@grove-dev/starlight 0.6.0 → 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.
- package/LICENSE +21 -0
- package/README.md +77 -78
- package/components/custom/LinkButton.astro +132 -87
- package/components/custom/dropdown/DropdownContent.astro +40 -40
- package/components/custom/dropdown/DropdownItem.astro +6 -1
- package/components/custom/dropdown/DropdownTrigger.astro +2 -2
- package/components/overrides/Hero.astro +30 -22
- package/components/overrides/PageFrame.astro +25 -9
- package/components/overrides/PageSidebar.astro +1 -0
- package/components/overrides/PageTitle.astro +1 -1
- package/components/overrides/Sidebar.astro +38 -1
- package/components/overrides/SocialIcons.astro +3 -1
- package/components/overrides/ThemeSelect.astro +4 -4
- package/components/overrides/TwoColumnContent.astro +1 -1
- package/components/overrides/parts/Drawer.astro +13 -7
- package/components/overrides/parts/NavBar.astro +11 -30
- package/components/overrides/parts/SidebarSublist.astro +73 -2
- package/core/config/docs-schema.ts +58 -0
- package/core/config/override.ts +10 -6
- package/core/config/schemas.ts +29 -8
- package/core/config/vite.ts +4 -4
- package/core/i18n.ts +175 -0
- package/core/plugin.ts +13 -11
- package/core/sidebar.ts +22 -0
- package/package.json +3 -19
- package/schema.ts +31 -0
- package/styles/base.css +35 -7
- package/styles/layers.css +1 -1
- package/styles/theme.css +2 -2
- package/user-components.ts +0 -1
- package/virtual.d.ts +3 -3
- package/THIRD_PARTY_LICENSES.md +0 -68
- package/components/custom/Card.astro +0 -118
package/core/i18n.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
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;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pick a value from a locale dictionary.
|
|
14
|
+
*
|
|
15
|
+
* Tries exact then case-insensitive matches for active locale candidates first,
|
|
16
|
+
* then the same for fallback/default candidates. This covers Starlight setups
|
|
17
|
+
* where config keys are BCP-47 (`es-ES`) but `Astro.currentLocale` is the path
|
|
18
|
+
* (`es-es`), without letting the default locale shadow an active case-insensitive hit.
|
|
19
|
+
*/
|
|
20
|
+
export function pickLocalized(
|
|
21
|
+
dictionary: Record<string, string> | undefined,
|
|
22
|
+
candidates: Array<string | undefined>,
|
|
23
|
+
fallbacks: Array<string | undefined> = []
|
|
24
|
+
): string | 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
|
+
}
|
|
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
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @deprecated Prefer `pickLocalized`. Kept for existing call sites/tests. */
|
|
65
|
+
export function pickLang(
|
|
66
|
+
dictionary: Record<string, string> | undefined,
|
|
67
|
+
lang: string
|
|
68
|
+
): string | undefined {
|
|
69
|
+
return pickLocalized(dictionary, [lang]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function activeKeys({ lang, locale }: LocaleLookup): Array<string | undefined> {
|
|
73
|
+
return [lang, locale];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fallbackKeys({ defaultLang, defaultLocale }: LocaleLookup): Array<string | undefined> {
|
|
77
|
+
return [defaultLang, defaultLocale];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a nav link label.
|
|
82
|
+
*
|
|
83
|
+
* Supports both APIs:
|
|
84
|
+
* - Starlight sidebar style: `label: string` + optional `translations`
|
|
85
|
+
* - Locale map style: `label: Record<BCP-47 | locale-path, string>`
|
|
86
|
+
*/
|
|
87
|
+
export function resolveNavLabel(
|
|
88
|
+
label: string | Record<string, string>,
|
|
89
|
+
translations: Record<string, string> | undefined,
|
|
90
|
+
keys: LocaleLookup
|
|
91
|
+
): string {
|
|
92
|
+
const primary = activeKeys(keys);
|
|
93
|
+
const fallback = fallbackKeys(keys);
|
|
94
|
+
|
|
95
|
+
if (typeof label === 'string') {
|
|
96
|
+
return pickLocalized(translations, primary, fallback) || label;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const resolved = pickLocalized(label, primary, fallback);
|
|
100
|
+
if (resolved) {
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Localized label must include a key for the default language "${keys.defaultLang}".`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a sidebar-style label: `translations[lang] ?? label`.
|
|
111
|
+
* Accepts a LocaleLookup or a bare lang string for convenience.
|
|
112
|
+
*/
|
|
113
|
+
export function resolveLabel(
|
|
114
|
+
label: string,
|
|
115
|
+
translations: Record<string, string> | undefined,
|
|
116
|
+
langOrKeys: string | LocaleLookup
|
|
117
|
+
): string {
|
|
118
|
+
if (typeof langOrKeys === 'string') {
|
|
119
|
+
return pickLocalized(translations, [langOrKeys]) || label;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return resolveNavLabel(label, translations, langOrKeys);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a title-style localized string (`string | Record<string, string>`).
|
|
127
|
+
* Prefers the active language/locale, then falls back to the default language.
|
|
128
|
+
*/
|
|
129
|
+
export function resolveLocalizedString(
|
|
130
|
+
value: string | Record<string, string>,
|
|
131
|
+
langOrKeys: string | LocaleLookup,
|
|
132
|
+
defaultLang?: string
|
|
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
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Build a LocaleLookup from Starlight + Astro locale values. */
|
|
154
|
+
export function createLocaleLookup(options: {
|
|
155
|
+
lang?: string;
|
|
156
|
+
locale?: string;
|
|
157
|
+
defaultLang?: string;
|
|
158
|
+
defaultLocale?: string;
|
|
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;
|
|
175
|
+
}
|
package/core/plugin.ts
CHANGED
|
@@ -3,30 +3,32 @@ import { override, COMPONENT_OVERRIDES } from './config/override';
|
|
|
3
3
|
import { expressiveCode } from './config/expresive-code';
|
|
4
4
|
import { vitePlugin } from './config/vite';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
type
|
|
8
|
-
type
|
|
6
|
+
GroveStarlightConfigSchema,
|
|
7
|
+
type GroveStarlightConfig,
|
|
8
|
+
type GroveStarlightUserConfig,
|
|
9
9
|
} from './config/schemas';
|
|
10
10
|
|
|
11
|
-
const parseConfig = (userConfig?:
|
|
12
|
-
const parsedConfig =
|
|
11
|
+
const parseConfig = (userConfig?: GroveStarlightUserConfig): GroveStarlightConfig => {
|
|
12
|
+
const parsedConfig = GroveStarlightConfigSchema.safeParse(userConfig ?? {});
|
|
13
13
|
|
|
14
14
|
if (!parsedConfig.success) {
|
|
15
15
|
throw new Error(
|
|
16
|
-
`The provided plugin configuration for
|
|
16
|
+
`The provided plugin configuration for grove-starlight is invalid.\n${parsedConfig.error.issues.map((issue) => issue.message).join('\n')}`
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
return parsedConfig.data;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
const plugin = (userConfig
|
|
23
|
+
const plugin = (userConfig: GroveStarlightUserConfig = {}): StarlightPlugin =>
|
|
24
24
|
({
|
|
25
|
-
name: '
|
|
25
|
+
name: 'grove-starlight',
|
|
26
26
|
hooks: {
|
|
27
27
|
'config:setup': ({ config, logger, updateConfig, addIntegration }) => {
|
|
28
|
+
const pluginConfig = parseConfig(userConfig);
|
|
29
|
+
|
|
28
30
|
updateConfig({
|
|
29
|
-
components: override(config, COMPONENT_OVERRIDES, logger),
|
|
31
|
+
components: override(config, pluginConfig, COMPONENT_OVERRIDES, logger),
|
|
30
32
|
customCss: [
|
|
31
33
|
...(config.customCss ?? []),
|
|
32
34
|
'@grove-dev/starlight/styles/layers',
|
|
@@ -37,11 +39,11 @@ const plugin = (userConfig?: LucodeStarlightUserConfig): StarlightPlugin =>
|
|
|
37
39
|
});
|
|
38
40
|
|
|
39
41
|
addIntegration({
|
|
40
|
-
name: '
|
|
42
|
+
name: 'grove-starlight-integration',
|
|
41
43
|
hooks: {
|
|
42
44
|
'astro:config:setup': ({ updateConfig }) => {
|
|
43
45
|
updateConfig({
|
|
44
|
-
vite: { plugins: [vitePlugin(
|
|
46
|
+
vite: { plugins: [vitePlugin(pluginConfig)] },
|
|
45
47
|
});
|
|
46
48
|
},
|
|
47
49
|
},
|
package/core/sidebar.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
2
|
+
|
|
3
|
+
export type SidebarEntry = StarlightRouteData['sidebar'][number];
|
|
4
|
+
export type SidebarGroup = Extract<SidebarEntry, { type: 'group' }>;
|
|
5
|
+
export type SidebarLink = Extract<SidebarEntry, { type: 'link' }>;
|
|
6
|
+
|
|
7
|
+
/** Every link reachable from `entries`, at any depth. */
|
|
8
|
+
export function flattenSidebar(entries: SidebarEntry[]): SidebarLink[] {
|
|
9
|
+
return entries.flatMap((entry) =>
|
|
10
|
+
entry.type === 'group' ? flattenSidebar(entry.entries) : [entry]
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether a group renders expanded.
|
|
16
|
+
*
|
|
17
|
+
* `collapsed` is the author's default, but a group holding the current page always opens, otherwise
|
|
18
|
+
* the reader would land on a page with no idea where they are in the tree.
|
|
19
|
+
*/
|
|
20
|
+
export function isSidebarGroupOpen(group: SidebarGroup): boolean {
|
|
21
|
+
return !group.collapsed || flattenSidebar(group.entries).some((link) => link.isCurrent);
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grove-dev/starlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Grove's theme for Starlight (the Astro native documentation site generator)",
|
|
6
6
|
"author": "grove-dev",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"node": ">=22.12.0"
|
|
10
10
|
},
|
|
11
11
|
"readme": "README.md",
|
|
12
|
-
"homepage": "https://
|
|
12
|
+
"homepage": "https://withgrove.dev/",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "https://github.com/tortuvshin/grove.git",
|
|
@@ -21,18 +21,6 @@
|
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
|
-
"files": [
|
|
25
|
-
"components",
|
|
26
|
-
"core",
|
|
27
|
-
"styles",
|
|
28
|
-
"global.d.ts",
|
|
29
|
-
"index.ts",
|
|
30
|
-
"schema.ts",
|
|
31
|
-
"user-components.ts",
|
|
32
|
-
"virtual.d.ts",
|
|
33
|
-
"README.md",
|
|
34
|
-
"THIRD_PARTY_LICENSES.md"
|
|
35
|
-
],
|
|
36
24
|
"exports": {
|
|
37
25
|
".": "./index.ts",
|
|
38
26
|
"./schema": "./schema.ts",
|
|
@@ -59,14 +47,10 @@
|
|
|
59
47
|
"./package.json": "./package.json"
|
|
60
48
|
},
|
|
61
49
|
"peerDependencies": {
|
|
62
|
-
"@astrojs/starlight": ">=0.
|
|
63
|
-
"astro": ">=5.0.0"
|
|
50
|
+
"@astrojs/starlight": ">=0.41.4"
|
|
64
51
|
},
|
|
65
52
|
"dependencies": {
|
|
66
53
|
"@pagefind/default-ui": "^1.5.2",
|
|
67
54
|
"marked": "^18.0.2"
|
|
68
|
-
},
|
|
69
|
-
"scripts": {
|
|
70
|
-
"test": "cd ../.. && pnpm exec vitest run --project starlight"
|
|
71
55
|
}
|
|
72
56
|
}
|
package/schema.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
|
+
import { markDocsSchemaLoaded } from './core/config/docs-schema';
|
|
3
|
+
|
|
4
|
+
// Lets `Hero.astro` tell "the user never wired up the schema" apart from "this page has no extra
|
|
5
|
+
// hero fields", so it only warns in the first case. See `core/config/docs-schema.ts`.
|
|
6
|
+
markDocsSchemaLoaded();
|
|
2
7
|
|
|
3
8
|
export const heroLayoutSchema = z
|
|
4
9
|
.enum(['centered', 'centered-top', 'split-left', 'split-right', 'banner'])
|
|
@@ -19,6 +24,32 @@ export const ExtendDocsSchema = z.object({
|
|
|
19
24
|
link: z.string(),
|
|
20
25
|
})
|
|
21
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([]),
|
|
22
53
|
})
|
|
23
54
|
.optional(),
|
|
24
55
|
});
|
package/styles/base.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@layer
|
|
1
|
+
@layer grove {
|
|
2
2
|
/* theme */
|
|
3
3
|
:root {
|
|
4
4
|
--tracking-tight: -0.025em;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
--radius: 0.625rem;
|
|
11
11
|
--header-height: calc(var(--spacing) * 14);
|
|
12
12
|
--sidebar-width: 18rem;
|
|
13
|
+
--content-max-width: 40rem;
|
|
13
14
|
--container-max-width: 1600px;
|
|
14
15
|
|
|
15
16
|
/* starlight color mappings */
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
--muted-foreground: var(--gray-3);
|
|
55
56
|
--accent: var(--gray-5);
|
|
56
57
|
--accent-foreground: var(--foreground);
|
|
58
|
+
--destructive: var(--red);
|
|
57
59
|
--border: var(--gray-6);
|
|
58
60
|
--input: var(--gray-8);
|
|
59
61
|
--ring: var(--gray-4);
|
|
@@ -330,11 +332,24 @@
|
|
|
330
332
|
/* built-in Starlight Cards and LinkCards */
|
|
331
333
|
.sl-markdown-content .card-grid {
|
|
332
334
|
gap: 1rem;
|
|
333
|
-
grid-auto-rows: 1fr;
|
|
334
335
|
}
|
|
335
336
|
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
/* Starlight sizes the two-up grid as `1fr 1fr`, which is really
|
|
338
|
+
`minmax(auto, 1fr)`: a track can never shrink below the min-content
|
|
339
|
+
width of what it holds. A card full of long inline code -- file paths
|
|
340
|
+
like `data/generated/records.full.json` -- therefore pushes its track
|
|
341
|
+
past half the content column, and the second card lands outside the
|
|
342
|
+
article, on top of the table of contents. `minmax(0, 1fr)` lets the
|
|
343
|
+
tracks shrink, and `anywhere` (not `break-word`, which does not affect
|
|
344
|
+
intrinsic sizing) lets the code inside them wrap to fit. */
|
|
345
|
+
@media (min-width: 50rem) {
|
|
346
|
+
.sl-markdown-content .card-grid {
|
|
347
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.sl-markdown-content :is(.card, .sl-link-card) :is(code, a) {
|
|
352
|
+
overflow-wrap: anywhere;
|
|
338
353
|
}
|
|
339
354
|
|
|
340
355
|
.sl-markdown-content :is(.card, .sl-link-card) {
|
|
@@ -344,7 +359,6 @@
|
|
|
344
359
|
background-color: var(--code-background);
|
|
345
360
|
box-shadow: none;
|
|
346
361
|
transition: background-color 0.15s;
|
|
347
|
-
height: 100%;
|
|
348
362
|
}
|
|
349
363
|
|
|
350
364
|
.sl-markdown-content :is(.card, .sl-link-card):hover {
|
|
@@ -736,21 +750,35 @@
|
|
|
736
750
|
display: none;
|
|
737
751
|
}
|
|
738
752
|
|
|
753
|
+
/* A step's heading sits on the same line as its number bullet. The wrapper
|
|
754
|
+
* only sets a *minimum* height: a fixed `height` clips any heading that is
|
|
755
|
+
* taller than the bullet (an `h2` step title) or wraps to a second line on
|
|
756
|
+
* narrow viewports, and the following block — usually a code frame — then
|
|
757
|
+
* paints over the overflowing text. */
|
|
739
758
|
.sl-markdown-content .sl-steps > li > .sl-heading-wrapper {
|
|
740
759
|
display: flex;
|
|
741
760
|
align-items: center;
|
|
742
761
|
gap: 0.5rem;
|
|
743
|
-
|
|
762
|
+
/* Matches the h3/h4 anchor icon: the level-h2 default (1.25rem) is
|
|
763
|
+
* sized for a full-size heading, not a step title. */
|
|
764
|
+
--sl-anchor-icon-size: 0.875rem;
|
|
744
765
|
min-height: var(--bullet-size);
|
|
745
766
|
transform: none;
|
|
746
767
|
margin-bottom: 0;
|
|
747
768
|
}
|
|
748
769
|
|
|
749
|
-
|
|
770
|
+
/* Step titles render at one size whatever heading level the page picked,
|
|
771
|
+
* so `## Step` and `### Step` look the same inside <Steps>. */
|
|
772
|
+
.sl-markdown-content .sl-steps > li > .sl-heading-wrapper :is(h1, h2, h3, h4, h5, h6) {
|
|
750
773
|
font-size: 1rem;
|
|
751
774
|
line-height: 1.75rem;
|
|
752
775
|
font-weight: 500;
|
|
753
776
|
margin: 0;
|
|
777
|
+
/* `h2` carries a `padding-top` for section spacing further up this file;
|
|
778
|
+
* inside a step it would push the title off the number bullet's line.
|
|
779
|
+
* Only the block padding is reset — the inline padding is the room
|
|
780
|
+
* Starlight reserves for the anchor-link icon. */
|
|
781
|
+
padding-block: 0;
|
|
754
782
|
}
|
|
755
783
|
|
|
756
784
|
.sl-markdown-content .sl-steps > li > .sl-heading-wrapper .sl-anchor-link {
|
package/styles/layers.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@layer starlight,
|
|
1
|
+
@layer starlight, grove;
|
package/styles/theme.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@layer
|
|
1
|
+
@layer grove {
|
|
2
2
|
:root[data-theme='dark'] {
|
|
3
3
|
/* starlight gray scale */
|
|
4
4
|
--foreground: oklch(98.5% 0 0); /* neutral-50 */
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
--orange: oklch(83.7% 0.128 66.29);
|
|
17
17
|
--orange-high: oklch(90.1% 0.076 70.697);
|
|
18
18
|
--green-low: oklch(26.6% 0.065 152.934);
|
|
19
|
-
--green: oklch(
|
|
19
|
+
--green: oklch(79.2% 0.209 151.711);
|
|
20
20
|
--green-high: oklch(87.1% 0.15 154.449);
|
|
21
21
|
--blue-low: oklch(29.3% 0.066 243.157);
|
|
22
22
|
--blue: oklch(70.7% 0.165 254.624);
|
package/user-components.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { default as Card } from './components/custom/Card.astro';
|
|
2
1
|
export { default as ContainerSection } from './components/custom/ContainerSection.astro';
|
|
3
2
|
export { default as LinkButton } from './components/custom/LinkButton.astro';
|
|
4
3
|
export { default as Dropdown } from './components/custom/dropdown';
|
package/virtual.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
declare module 'virtual:
|
|
2
|
-
const
|
|
3
|
-
export default
|
|
1
|
+
declare module 'virtual:grove-starlight-config' {
|
|
2
|
+
const GroveStarlightConfig: import('./core/config/schemas').GroveStarlightConfig;
|
|
3
|
+
export default GroveStarlightConfig;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
declare module 'virtual:starlight/user-config' {
|
package/THIRD_PARTY_LICENSES.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Third-party attributions — `@grove-dev/starlight`
|
|
2
|
-
|
|
3
|
-
This package incorporates design and code from upstream projects. The
|
|
4
|
-
contributions are credited below; reproduction of the upstream license
|
|
5
|
-
texts follows the attribution block. The Grove project ships
|
|
6
|
-
`@grove-dev/starlight` under the same MIT license that the rest of the
|
|
7
|
-
monorepo uses; the third-party license texts below are reproduced here
|
|
8
|
-
to satisfy the upstream attribution requirements.
|
|
9
|
-
|
|
10
|
-
> **⚠️ TODO(decision): license compatibility check.**
|
|
11
|
-
> The audit and the project's own README credit the upstream work as
|
|
12
|
-
> "lucode / lucas-labs" and reference
|
|
13
|
-
> `https://github.com/lucas-labs/@grove-dev/starlight-theme`. Before the
|
|
14
|
-
> first publish of `@grove-dev/starlight` to npm, the project owner
|
|
15
|
-
> must:
|
|
16
|
-
>
|
|
17
|
-
> 1. Confirm the exact upstream repo URL (the `lucas-labs/...` path
|
|
18
|
-
> in the README is a placeholder; the real upstream is
|
|
19
|
-
> `lucode-labs/lucode` or similar — verify on GitHub).
|
|
20
|
-
> 2. Verify the upstream license (MIT? Apache-2.0? something else?)
|
|
21
|
-
> and confirm it is compatible with the Grove monorepo's MIT
|
|
22
|
-
> distribution.
|
|
23
|
-
> 3. If compatible, replace the `<!-- TODO LICENSE TEXT -->` block
|
|
24
|
-
> below with the verbatim upstream license text (most MIT projects
|
|
25
|
-
> use the standard MIT text — the canonical version is at
|
|
26
|
-
> <https://opensource.org/licenses/MIT>).
|
|
27
|
-
> 4. If not compatible, either obtain a written license grant from
|
|
28
|
-
> the upstream maintainer OR replace the derivative design with
|
|
29
|
-
> original work before publishing.
|
|
30
|
-
>
|
|
31
|
-
> Tracking issue:
|
|
32
|
-
> <https://github.com/tortuvshin/grove/issues/new?title=starlight%3A+verify+third-party+license+compatibility>
|
|
33
|
-
|
|
34
|
-
## Upstream credits
|
|
35
|
-
|
|
36
|
-
The design and component overrides in this package derive from:
|
|
37
|
-
|
|
38
|
-
- **[lucode](https://github.com/lucas-labs/@grove-dev/starlight-theme)** by the
|
|
39
|
-
**lucas-labs** organization (also referenced as "Lucode" in the
|
|
40
|
-
package's `user-components.ts` and component overrides). The theme
|
|
41
|
-
recreates the design of [shadcn/ui](https://ui.shadcn.com/) (MIT)
|
|
42
|
-
for use inside Astro Starlight, with custom overrides for the
|
|
43
|
-
header, sidebar, page frame, hero, footer, search, table of
|
|
44
|
-
contents, pagination, and Markdown content. See the package
|
|
45
|
-
`README.md` (the "Attribution" and "Usage" sections) for the
|
|
46
|
-
in-code pointers.
|
|
47
|
-
- **[adrian-ub/starlight-theme-black](https://github.com/adrian-ub/starlight-theme-black)**
|
|
48
|
-
— the earlier shadcn/ui-inspired Starlight theme that
|
|
49
|
-
`lucas-labs/@grove-dev/starlight-theme` was based on (per the
|
|
50
|
-
upstream README).
|
|
51
|
-
- **[shadcn/ui](https://ui.shadcn.com/)** — the original design
|
|
52
|
-
language that the upstream work and this package both target.
|
|
53
|
-
|
|
54
|
-
## Upstream license
|
|
55
|
-
|
|
56
|
-
<!-- TODO LICENSE TEXT: paste the verbatim upstream license (typically
|
|
57
|
-
the standard MIT text with copyright line) here once the license
|
|
58
|
-
compatibility check above is complete. Until then this section is
|
|
59
|
-
intentionally empty so the file can ship without misrepresenting the
|
|
60
|
-
upstream license. -->
|
|
61
|
-
|
|
62
|
-
## License for this package
|
|
63
|
-
|
|
64
|
-
`@grove-dev/starlight` is distributed under the **MIT License** — see
|
|
65
|
-
[`../core/LICENSE`](../../LICENSE) at the monorepo root for the
|
|
66
|
-
canonical text. The third-party attributions above are reproduced for
|
|
67
|
-
compliance with the upstream license terms; they do not change the
|
|
68
|
-
license of the rest of the package.
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* Starlight `<Card>` override that adds an optional `href` prop.
|
|
4
|
-
*
|
|
5
|
-
* The upstream `Card.astro` in `@astrojs/starlight@0.40` only accepts
|
|
6
|
-
* `icon` and `title`, so a `<Card title="..." href="...">` call silently
|
|
7
|
-
* dropped the link and rendered a non-clickable `<article>`. We wrap the
|
|
8
|
-
* inner content in an `<a>` when `href` is present so the entire card
|
|
9
|
-
* surface is interactive (the same affordance the `LinkCard` component
|
|
10
|
-
* uses, but with our visual `Card` styling).
|
|
11
|
-
*
|
|
12
|
-
* Visual styles are identical to upstream — only the structure gains a
|
|
13
|
-
* single `<a>` wrapper when `href` is set.
|
|
14
|
-
*/
|
|
15
|
-
import { Icon } from '@astrojs/starlight/components';
|
|
16
|
-
import type { StarlightIcon } from '@astrojs/starlight/components-internals/Icons';
|
|
17
|
-
|
|
18
|
-
interface Props {
|
|
19
|
-
icon?: StarlightIcon;
|
|
20
|
-
title: string;
|
|
21
|
-
href?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const { icon, title, href } = Astro.props;
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
<article class="card sl-flex">
|
|
28
|
-
<p class="title sl-flex">
|
|
29
|
-
{icon && <Icon name={icon} class="icon" size="1.333em" />}
|
|
30
|
-
<span>{title}</span>
|
|
31
|
-
</p>
|
|
32
|
-
<div class="body">
|
|
33
|
-
{href ? <a {href} class="sl-card-link"><slot /></a> : <slot />}
|
|
34
|
-
</div>
|
|
35
|
-
</article>
|
|
36
|
-
|
|
37
|
-
<style>
|
|
38
|
-
@layer starlight.components {
|
|
39
|
-
.card {
|
|
40
|
-
--sl-card-border: var(--sl-color-purple);
|
|
41
|
-
--sl-card-bg: var(--sl-color-purple-low);
|
|
42
|
-
border: 1px solid var(--sl-color-gray-5);
|
|
43
|
-
background-color: var(--sl-color-black);
|
|
44
|
-
padding: clamp(1rem, calc(0.125rem + 3vw), 2.5rem);
|
|
45
|
-
flex-direction: column;
|
|
46
|
-
gap: clamp(0.5rem, calc(0.125rem + 1vw), 1rem);
|
|
47
|
-
transition: border-color 0.15s ease, transform 0.15s ease;
|
|
48
|
-
}
|
|
49
|
-
.card:nth-child(4n + 1) {
|
|
50
|
-
--sl-card-border: var(--sl-color-orange);
|
|
51
|
-
--sl-card-bg: var(--sl-color-orange-low);
|
|
52
|
-
}
|
|
53
|
-
.card:nth-child(4n + 3) {
|
|
54
|
-
--sl-card-border: var(--sl-color-green);
|
|
55
|
-
--sl-card-bg: var(--sl-color-green-low);
|
|
56
|
-
}
|
|
57
|
-
.card:nth-child(4n + 4) {
|
|
58
|
-
--sl-card-border: var(--sl-color-red);
|
|
59
|
-
--sl-card-bg: var(--sl-color-red-low);
|
|
60
|
-
}
|
|
61
|
-
.card:nth-child(4n + 5) {
|
|
62
|
-
--sl-card-border: var(--sl-color-blue);
|
|
63
|
-
--sl-card-bg: var(--sl-color-blue-low);
|
|
64
|
-
}
|
|
65
|
-
.title {
|
|
66
|
-
font-weight: 600;
|
|
67
|
-
font-size: var(--sl-text-h4);
|
|
68
|
-
color: var(--sl-color-white);
|
|
69
|
-
line-height: var(--sl-line-height-headings);
|
|
70
|
-
gap: 1rem;
|
|
71
|
-
align-items: center;
|
|
72
|
-
}
|
|
73
|
-
.card .icon {
|
|
74
|
-
border: 1px solid var(--sl-card-border);
|
|
75
|
-
background-color: var(--sl-card-bg);
|
|
76
|
-
padding: 0.2em;
|
|
77
|
-
border-radius: 0.25rem;
|
|
78
|
-
flex-shrink: 0;
|
|
79
|
-
}
|
|
80
|
-
.card .body {
|
|
81
|
-
margin: 0;
|
|
82
|
-
font-size: clamp(var(--sl-text-sm), calc(0.5rem + 1vw), var(--sl-text-body));
|
|
83
|
-
}
|
|
84
|
-
.card .body .sl-card-link {
|
|
85
|
-
/* The link is invisible text-decoration-wise; the card itself
|
|
86
|
-
* is the click target thanks to the ::before pseudo-element
|
|
87
|
-
* below. Display:contents lets the link wrap the body text
|
|
88
|
-
* without breaking the flex/grid layout. */
|
|
89
|
-
display: contents;
|
|
90
|
-
color: inherit;
|
|
91
|
-
}
|
|
92
|
-
.card:has(.sl-card-link) {
|
|
93
|
-
position: relative;
|
|
94
|
-
cursor: pointer;
|
|
95
|
-
}
|
|
96
|
-
.card:has(.sl-card-link):hover {
|
|
97
|
-
border-color: var(--sl-color-gray-2);
|
|
98
|
-
}
|
|
99
|
-
.card:has(.sl-card-link):hover .title {
|
|
100
|
-
color: var(--sl-color-accent-high);
|
|
101
|
-
}
|
|
102
|
-
/* a11y: a transparent overlay makes the whole card clickable
|
|
103
|
-
* without changing layout. Same trick Starlight's LinkCard uses. */
|
|
104
|
-
.card:has(.sl-card-link) .sl-card-link::before {
|
|
105
|
-
content: '';
|
|
106
|
-
position: absolute;
|
|
107
|
-
inset: 0;
|
|
108
|
-
border-radius: inherit;
|
|
109
|
-
}
|
|
110
|
-
/* Focus-visible ring anchored to the card surface so keyboard
|
|
111
|
-
* users see a clear focus indicator (otherwise the transparent
|
|
112
|
-
* overlay hides the default link outline). */
|
|
113
|
-
.card:has(.sl-card-link:focus-visible) {
|
|
114
|
-
outline: 2px solid var(--sl-color-accent);
|
|
115
|
-
outline-offset: 2px;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
</style>
|