Skip to content

Commit a3941f8

Browse files
authored
feat: add support to scaffold themes (#15)
1 parent b67bddf commit a3941f8

File tree

7 files changed

+144
-7
lines changed

7 files changed

+144
-7
lines changed

.changeset/tough-hats-enjoy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hideoo/generator-starlight-plugin': minor
3+
---
4+
5+
Adds support for scaffolding themes.

src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Generator, { type BaseOptions } from 'yeoman-generator'
44

55
import { copy, copyTpl } from './libs/fs.js'
66
import { fetchDependencyVersions } from './libs/npm.js'
7-
import { promptForEmoji, promptForName, promptForText } from './libs/prompt.js'
7+
import { promptForEmoji, promptForLayer, promptForName, promptForText, promptForTheme } from './libs/prompt.js'
88

99
export default class StarlightPluginGenerator extends Generator<BaseOptions & Configuration> {
1010
configuration: Configuration
@@ -25,6 +25,8 @@ export default class StarlightPluginGenerator extends Generator<BaseOptions & Co
2525
type: String,
2626
description: 'Single emoji representing the Starlight plugin (used in the documentation)',
2727
})
28+
this.option('theme', { type: Boolean, description: 'Define if the plugin is a theme' })
29+
this.option('layer', { type: String, description: 'Name of the theme CSS cascade layer (only used for themes)' })
2830
this.option('ghUsername', { type: String, description: 'GitHub username' })
2931
}
3032

@@ -41,6 +43,8 @@ export default class StarlightPluginGenerator extends Generator<BaseOptions & Co
4143
'My awesome Starlight plugin',
4244
)
4345
await promptForEmoji(this)
46+
await promptForTheme(this)
47+
if (this.configuration.theme) await promptForLayer(this)
4448
await promptForText(this, 'ghUsername', 'What is your GitHub username?', 'ghost')
4549
}
4650

@@ -66,6 +70,10 @@ export default class StarlightPluginGenerator extends Generator<BaseOptions & Co
6670

6771
copyTpl(this, 'packages/plugin', pluginPath)
6872
copy(this, 'npmignore', `${pluginPath}/.npmignore`)
73+
74+
if (this.configuration.theme) {
75+
copyTpl(this, 'styles.css', `${pluginPath}/styles.css`)
76+
}
6977
}
7078

7179
install() {
@@ -89,5 +97,7 @@ export interface Configuration {
8997
description?: string
9098
emoji?: string
9199
ghUsername?: string
100+
layer?: string
101+
theme?: boolean
92102
year: string
93103
}

src/libs/prompt.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type StarlightPluginGenerator from '../index.js'
22
import type { Configuration } from '../index.js'
33

4-
import { validateEmoji, validateName, validateNonEmptyString } from './validator.js'
4+
import { validateEmoji, validateLayer, validateName, validateNonEmptyString } from './validator.js'
55

66
export async function promptForName(generator: StarlightPluginGenerator) {
77
const name = generator.options.name
@@ -15,16 +15,38 @@ export async function promptForName(generator: StarlightPluginGenerator) {
1515
type: 'input',
1616
name: 'name',
1717
message: 'What is the name of your Starlight plugin?',
18-
default: `starlight-plugin-name`,
18+
default: 'starlight-plugin-name',
1919
validate: validateName,
2020
})
2121

2222
generator.configuration.name = answers.name
2323
}
2424

25+
export async function promptForTheme(generator: StarlightPluginGenerator) {
26+
const theme = generator.options.theme
27+
28+
if (theme !== undefined) {
29+
generator.configuration.theme = theme
30+
return
31+
}
32+
33+
const answers = await generator.prompt<{ theme: boolean }>({
34+
type: 'list',
35+
name: 'theme',
36+
message: 'Is your plugin a theme?',
37+
choices: [
38+
{ name: 'Yes', value: true },
39+
{ name: 'No', value: false },
40+
],
41+
default: false,
42+
})
43+
44+
generator.configuration.theme = answers.theme
45+
}
46+
2547
export async function promptForText(
2648
generator: StarlightPluginGenerator,
27-
key: keyof Configuration,
49+
key: TextConfigurationKeys,
2850
message: string,
2951
defaultValue: string,
3052
) {
@@ -65,3 +87,28 @@ export async function promptForEmoji(generator: StarlightPluginGenerator) {
6587

6688
generator.configuration.emoji = answers.emoji
6789
}
90+
91+
export async function promptForLayer(generator: StarlightPluginGenerator) {
92+
const layer = generator.options.layer
93+
94+
if (layer && validateLayer(layer) === true) {
95+
generator.configuration.layer = layer
96+
return
97+
}
98+
99+
const answers = await generator.prompt<{ layer: string }>({
100+
type: 'input',
101+
name: 'layer',
102+
message: 'What is the name of your theme CSS cascade layer?',
103+
default: `my-theme`,
104+
validate: validateLayer,
105+
})
106+
107+
generator.configuration.layer = answers.layer
108+
}
109+
110+
type TextConfigurationKeys = NonNullable<
111+
{
112+
[K in keyof Configuration]: Configuration[K] extends string | undefined ? K : never
113+
}[keyof Configuration]
114+
>

src/libs/validator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const nameValidationRegex = /^(?:@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*$/
22
const emojiValidationRegex = /^\p{Emoji_Presentation}\p{Emoji_Modifier}*$/u
3+
const layerValidationRegex = /^[a-zA-Z][_a-zA-Z0-9-]*$/
34

45
export function validateName(name: string) {
56
return nameValidationRegex.test(name) ? true : 'Invalid plugin name'
@@ -12,3 +13,7 @@ export function validateNonEmptyString(value: string) {
1213
export function validateEmoji(emoji: string) {
1314
return emojiValidationRegex.test(emoji) ? true : 'Invalid emoji'
1415
}
16+
17+
export function validateLayer(name: string) {
18+
return layerValidationRegex.test(name) ? true : 'Invalid theme CSS cascade layer name'
19+
}

templates/packages/plugin/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default function <%= importName %>(): StarlightPlugin {
44
return {
55
name: '<%= name %>',
66
hooks: {
7-
'config:setup'({ logger }) {
7+
'config:setup'({ <% if(theme){ %>config, logger, updateConfig<% } else{ %>logger<% } %> }) {
88
/**
99
* This is the entry point of your Starlight plugin.
1010
* The `config:setup` hook is called when Starlight is initialized (during the Astro `astro:config:setup`
@@ -15,7 +15,17 @@ export default function <%= importName %>(): StarlightPlugin {
1515
* @see https://starlight.astro.build/reference/plugins/
1616
*/
1717
logger.info('Hello from the <%= name %> plugin!')
18-
},
18+
<% if (theme) { %>
19+
/**
20+
* Update the provided Starlight user configuration by appending the theme CSS file to the `customCss` array.
21+
*
22+
* @see https://starlight.astro.build/reference/plugins/#updateconfig
23+
* @see https://starlight.astro.build/reference/configuration/#customcss
24+
*/
25+
updateConfig({
26+
customCss: [...(config.customCss ?? []), '<%= name %>/styles'],
27+
})
28+
<% } -%> },
1929
},
2030
}
2131
}

templates/packages/plugin/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"author": "<%= ghUsername %>",
77
"type": "module",
88
"exports": {
9-
".": "./index.ts"
9+
".": "./index.ts"<% if (theme) { %>,
10+
"./styles": "./styles.css"<% } %>
1011
},
1112
"devDependencies": {
1213
<%- dep("@astrojs/starlight") %>,

templates/styles.css

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Just like Starlight uses cascade layers to manage the order of its styles, the theme will also use its own layer.
3+
* We first define a default, but still overridable, layer order for the theme relative to Starlight's layers.
4+
*
5+
* @see https://starlight.astro.build/guides/css-and-tailwind/#cascade-layers
6+
* @see https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Cascade_layers
7+
*/
8+
@layer starlight, <%= layer %>;
9+
10+
/**
11+
* Scope the theme styles to its own layer so that user styles can override them easily.
12+
*/
13+
@layer <%= layer %> {
14+
:root,
15+
::backdrop {
16+
/**
17+
* Override some CSS custom properties used by Starlight for the dark theme.
18+
*
19+
* @see https://github.com/withastro/starlight/blob/main/packages/starlight/style/props.css
20+
*/
21+
--sl-color-accent-low: #002b27;
22+
--sl-color-accent: #007a72;
23+
--sl-color-accent-high: #8cdad0;
24+
--sl-color-white: #ffffff;
25+
--sl-color-gray-1: #ffe6ec;
26+
--sl-color-gray-2: #edadbf;
27+
--sl-color-gray-3: #d45d86;
28+
--sl-color-gray-4: #982353;
29+
--sl-color-gray-5: #6b0034;
30+
--sl-color-gray-6: #4d0024;
31+
--sl-color-black: #320316;
32+
}
33+
34+
:root[data-theme='light'],
35+
[data-theme='light'] ::backdrop {
36+
/**
37+
* Override some CSS custom properties used by Starlight for the light theme.
38+
*/
39+
--sl-color-accent-low: #abe4dc;
40+
--sl-color-accent: #007169;
41+
--sl-color-accent-high: #003b36;
42+
--sl-color-white: #320316;
43+
--sl-color-gray-1: #4d0024;
44+
--sl-color-gray-2: #6b0034;
45+
--sl-color-gray-3: #982353;
46+
--sl-color-gray-4: #d45d86;
47+
--sl-color-gray-5: #edadbf;
48+
--sl-color-gray-6: #ffe6ec;
49+
--sl-color-gray-7: #fff3f6;
50+
--sl-color-black: #ffffff;
51+
}
52+
53+
/**
54+
* Add rounded corners to cards.
55+
*/
56+
.card {
57+
border-radius: 0.75rem;
58+
}
59+
}

0 commit comments

Comments
 (0)