@docpensieve/theme 0.1.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 +56 -0
- package/package.json +50 -0
- package/src/base-provider.js +62 -0
- package/src/custom-provider.js +69 -0
- package/src/index.js +17 -0
- package/src/styles.js +45 -0
- package/src/tailwind-provider.js +167 -0
- package/src/theme-engine.js +116 -0
- package/styles/custom.css +280 -0
- package/styles/prose.css +109 -0
- package/styles/structure.css +235 -0
- package/styles/tailwind-bridge.css +131 -0
- package/types/base-provider.d.ts +66 -0
- package/types/custom-provider.d.ts +66 -0
- package/types/index.d.ts +17 -0
- package/types/styles.d.ts +20 -0
- package/types/tailwind-provider.d.ts +48 -0
- package/types/theme-engine.d.ts +51 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Bridge between the DocPensieve shell and Tailwind.
|
|
3
|
+
*
|
|
4
|
+
* Tailwind dresses the shell through utilities set on the slots: this file
|
|
5
|
+
* only holds what a utility cannot express — the descendant selectors of the
|
|
6
|
+
* menu — and the dark-mode palette of the --dp-* tokens, which Tailwind does
|
|
7
|
+
* not emit on its own.
|
|
8
|
+
*
|
|
9
|
+
* The article typography comes from prose.css: Tailwind's Preflight resets
|
|
10
|
+
* headings, lists and quotes.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* --- Dark mode ----------------------------------------------------------- */
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Tailwind v4 varies `dark:` on the system preference. These blocks follow
|
|
17
|
+
* the same rule for the shared tokens, with the `.light` / `.dark` escape
|
|
18
|
+
* hatch that theme.darkMode allows.
|
|
19
|
+
*/
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root:not(.light) {
|
|
22
|
+
--dp-bg: oklch(12.9% 0.042 264.695);
|
|
23
|
+
--dp-bg-soft: oklch(20.8% 0.042 265.755);
|
|
24
|
+
--dp-text: oklch(96.8% 0.007 247.896);
|
|
25
|
+
--dp-text-soft: oklch(70.4% 0.04 256.788);
|
|
26
|
+
--dp-border: oklch(27.9% 0.041 260.031);
|
|
27
|
+
--dp-rule: oklch(27.9% 0.041 260.031);
|
|
28
|
+
--dp-accent: oklch(78.5% 0.115 274.713);
|
|
29
|
+
--dp-accent-soft: oklch(20.8% 0.042 265.755);
|
|
30
|
+
--dp-shadow: rgba(0, 0, 0, 0.45);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:root.dark {
|
|
35
|
+
--dp-bg: oklch(12.9% 0.042 264.695);
|
|
36
|
+
--dp-bg-soft: oklch(20.8% 0.042 265.755);
|
|
37
|
+
--dp-text: oklch(96.8% 0.007 247.896);
|
|
38
|
+
--dp-text-soft: oklch(70.4% 0.04 256.788);
|
|
39
|
+
--dp-border: oklch(27.9% 0.041 260.031);
|
|
40
|
+
--dp-rule: oklch(27.9% 0.041 260.031);
|
|
41
|
+
--dp-accent: oklch(78.5% 0.115 274.713);
|
|
42
|
+
--dp-accent-soft: oklch(20.8% 0.042 265.755);
|
|
43
|
+
--dp-shadow: rgba(0, 0, 0, 0.45);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
body {
|
|
47
|
+
background-color: var(--dp-bg);
|
|
48
|
+
color: var(--dp-text);
|
|
49
|
+
font-family: var(--dp-font);
|
|
50
|
+
line-height: 1.7;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
a {
|
|
54
|
+
color: var(--dp-accent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* --- Side menu ----------------------------------------------------------- */
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* A utility applies to one element, not to a relation between elements: the
|
|
61
|
+
* rule linking a child to its parent can only come from here.
|
|
62
|
+
*/
|
|
63
|
+
.dp-nav .dp-nav {
|
|
64
|
+
margin: 0.1rem 0 0.35rem 0.35rem;
|
|
65
|
+
padding-left: 0.85rem;
|
|
66
|
+
border-left: 1px solid var(--dp-rule);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dp-nav,
|
|
70
|
+
.dp-toc-list {
|
|
71
|
+
margin: 0;
|
|
72
|
+
padding: 0;
|
|
73
|
+
list-style: none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* The bar of the current page sits on the parent's rule, in its place. */
|
|
77
|
+
.dp-nav .dp-nav .dp-nav-link[aria-current='page'] {
|
|
78
|
+
position: relative;
|
|
79
|
+
}
|
|
80
|
+
.dp-nav .dp-nav .dp-nav-link[aria-current='page']::before {
|
|
81
|
+
content: '';
|
|
82
|
+
position: absolute;
|
|
83
|
+
left: calc(-0.85rem - 1px);
|
|
84
|
+
top: 0;
|
|
85
|
+
bottom: 0;
|
|
86
|
+
width: 2px;
|
|
87
|
+
background: var(--dp-accent);
|
|
88
|
+
border-radius: 2px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* --- Version switcher ---------------------------------------------------- */
|
|
92
|
+
|
|
93
|
+
.dp-versions > summary::after {
|
|
94
|
+
content: '';
|
|
95
|
+
width: 0.4rem;
|
|
96
|
+
height: 0.4rem;
|
|
97
|
+
margin-top: -0.2rem;
|
|
98
|
+
border-right: 1.5px solid currentColor;
|
|
99
|
+
border-bottom: 1.5px solid currentColor;
|
|
100
|
+
transform: rotate(45deg);
|
|
101
|
+
}
|
|
102
|
+
.dp-versions[open] > summary::after {
|
|
103
|
+
transform: rotate(-135deg);
|
|
104
|
+
margin-top: 0.15rem;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* --- Table of contents --------------------------------------------------- */
|
|
108
|
+
|
|
109
|
+
.dp-toc-list .dp-toc-list {
|
|
110
|
+
border-left: 0;
|
|
111
|
+
padding-left: 0.8rem;
|
|
112
|
+
}
|
|
113
|
+
.dp-toc-item a {
|
|
114
|
+
display: block;
|
|
115
|
+
padding: 0.18rem 0 0.18rem 0.8rem;
|
|
116
|
+
margin-left: -1px;
|
|
117
|
+
border-left: 1px solid transparent;
|
|
118
|
+
color: var(--dp-text-soft);
|
|
119
|
+
text-decoration: none;
|
|
120
|
+
line-height: 1.45;
|
|
121
|
+
}
|
|
122
|
+
.dp-toc-item a:hover {
|
|
123
|
+
color: var(--dp-accent);
|
|
124
|
+
border-left-color: var(--dp-accent);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@media (max-width: 56rem) {
|
|
128
|
+
.dp-sidebar {
|
|
129
|
+
border-bottom: 1px solid var(--dp-border);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract shared by every theme provider.
|
|
3
|
+
* @module @docpensieve/theme/base-provider
|
|
4
|
+
*/
|
|
5
|
+
export type ThemeOutput = {
|
|
6
|
+
/**
|
|
7
|
+
* CSS to concatenate into the final stylesheet.
|
|
8
|
+
*/
|
|
9
|
+
css: string;
|
|
10
|
+
/**
|
|
11
|
+
* Exposed CSS variables (`--dp-*`).
|
|
12
|
+
*/
|
|
13
|
+
variables: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
export type CompileContext = {
|
|
16
|
+
/**
|
|
17
|
+
* Class names collected from the rendered
|
|
18
|
+
* pages. A utility provider, Tailwind first and foremost, only emits the
|
|
19
|
+
* matching rules; the others ignore them.
|
|
20
|
+
*/
|
|
21
|
+
candidates?: string[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} ThemeOutput
|
|
25
|
+
* @property {string} css CSS to concatenate into the final stylesheet.
|
|
26
|
+
* @property {Record<string, string>} variables Exposed CSS variables (`--dp-*`).
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} CompileContext
|
|
30
|
+
* @property {string[]} [candidates] Class names collected from the rendered
|
|
31
|
+
* pages. A utility provider, Tailwind first and foremost, only emits the
|
|
32
|
+
* matching rules; the others ignore them.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Base class to extend in order to plug in a CSS framework.
|
|
36
|
+
*
|
|
37
|
+
* A provider generates no HTML: it supplies CSS, variables and a table of
|
|
38
|
+
* class aliases. That is what lets a single template render correctly under
|
|
39
|
+
* Tailwind as well as under the custom theme.
|
|
40
|
+
*/
|
|
41
|
+
export declare class BaseThemeProvider {
|
|
42
|
+
options: Record<string, any>;
|
|
43
|
+
/** Short provider identifier, unique within the engine. */
|
|
44
|
+
static id: string;
|
|
45
|
+
/** @param {Record<string, any>} [options] Options taken from `config.theme`. */
|
|
46
|
+
constructor(options?: Record<string, any>);
|
|
47
|
+
/**
|
|
48
|
+
* Class aliases this provider imposes on the shell slots.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately synchronous and outside `compile()`: templates need these
|
|
51
|
+
* classes to be rendered, and a utility provider needs the rendered pages to
|
|
52
|
+
* compile its CSS. Keeping them apart breaks that circular dependency.
|
|
53
|
+
*
|
|
54
|
+
* @returns {Record<string, string>} Redefined slots, the others falling back
|
|
55
|
+
* to `DEFAULT_THEME_CLASSES`.
|
|
56
|
+
*/
|
|
57
|
+
get classes(): Record<string, string>;
|
|
58
|
+
/**
|
|
59
|
+
* Produces the provider's CSS contribution.
|
|
60
|
+
*
|
|
61
|
+
* @param {CompileContext} [_context]
|
|
62
|
+
* @returns {Promise<ThemeOutput>}
|
|
63
|
+
* @abstract
|
|
64
|
+
*/
|
|
65
|
+
compile(_context?: CompileContext): Promise<ThemeOutput>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocPensieve's custom theme, with no external dependency.
|
|
3
|
+
*
|
|
4
|
+
* @module @docpensieve/theme/custom-provider
|
|
5
|
+
*/
|
|
6
|
+
import { BaseThemeProvider } from './base-provider.js';
|
|
7
|
+
/**
|
|
8
|
+
* Palette and measures of the light theme.
|
|
9
|
+
*
|
|
10
|
+
* Dark mode does not live here: it fits in two blocks of `custom.css`, since
|
|
11
|
+
* a flat table of variables cannot express a media query.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_TOKENS: Readonly<{
|
|
14
|
+
'--dp-bg': "#ffffff";
|
|
15
|
+
'--dp-bg-soft': "#f7f8fa";
|
|
16
|
+
'--dp-text': "#1c1e21";
|
|
17
|
+
'--dp-text-soft': "#5f6773";
|
|
18
|
+
'--dp-border': "#e3e6ea";
|
|
19
|
+
'--dp-rule': "#e8ebef";
|
|
20
|
+
'--dp-accent': "#5b57d1";
|
|
21
|
+
'--dp-accent-soft': "#f0effc";
|
|
22
|
+
'--dp-shadow': "rgba(20, 24, 34, 0.12)";
|
|
23
|
+
'--dp-radius': "6px";
|
|
24
|
+
'--dp-font': "system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
|
|
25
|
+
'--dp-font-mono': "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
26
|
+
'--dp-content-width': "none";
|
|
27
|
+
'--dp-sidebar-width': "15.5rem";
|
|
28
|
+
'--dp-toc-width': "13rem";
|
|
29
|
+
}>;
|
|
30
|
+
/** Custom theme: hand-written CSS, no dependency. */
|
|
31
|
+
export declare class CustomProvider extends BaseThemeProvider {
|
|
32
|
+
tokens: {
|
|
33
|
+
'--dp-bg': "#ffffff";
|
|
34
|
+
'--dp-bg-soft': "#f7f8fa";
|
|
35
|
+
'--dp-text': "#1c1e21";
|
|
36
|
+
'--dp-text-soft': "#5f6773";
|
|
37
|
+
'--dp-border': "#e3e6ea";
|
|
38
|
+
'--dp-rule': "#e8ebef";
|
|
39
|
+
'--dp-accent': "#5b57d1";
|
|
40
|
+
'--dp-accent-soft': "#f0effc";
|
|
41
|
+
'--dp-shadow': "rgba(20, 24, 34, 0.12)";
|
|
42
|
+
'--dp-radius': "6px";
|
|
43
|
+
'--dp-font': "system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
|
|
44
|
+
'--dp-font-mono': "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
45
|
+
'--dp-content-width': "none";
|
|
46
|
+
'--dp-sidebar-width': "15.5rem";
|
|
47
|
+
'--dp-toc-width': "13rem";
|
|
48
|
+
};
|
|
49
|
+
extraCss: string;
|
|
50
|
+
static id: string;
|
|
51
|
+
/**
|
|
52
|
+
* @param {{ tokens?: Record<string, string>, css?: string }} [options]
|
|
53
|
+
* `tokens` overrides the palette and the measures — keys without a
|
|
54
|
+
* leading `--` are prefixed by the engine. `css` is appended after the
|
|
55
|
+
* default stylesheet, so it wins at equal specificity.
|
|
56
|
+
*/
|
|
57
|
+
constructor(options?: {
|
|
58
|
+
tokens?: Record<string, string>;
|
|
59
|
+
css?: string;
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* @returns {Promise<import('./base-provider.js').ThemeOutput>}
|
|
63
|
+
* @throws {ThemeError} When a stylesheet of the package is missing.
|
|
64
|
+
*/
|
|
65
|
+
compile(): Promise<import('./base-provider.js').ThemeOutput>;
|
|
66
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @docpensieve/theme — composable theme providers.
|
|
3
|
+
* @module @docpensieve/theme
|
|
4
|
+
*/
|
|
5
|
+
export type ThemeOutput = import('./base-provider.js').ThemeOutput;
|
|
6
|
+
export type CompileContext = import('./base-provider.js').CompileContext;
|
|
7
|
+
/**
|
|
8
|
+
* Types of the provider contract, re-exported for consumers of the published
|
|
9
|
+
* package: without this they would only be reachable through an internal path.
|
|
10
|
+
*
|
|
11
|
+
* @typedef {import('./base-provider.js').ThemeOutput} ThemeOutput
|
|
12
|
+
* @typedef {import('./base-provider.js').CompileContext} CompileContext
|
|
13
|
+
*/
|
|
14
|
+
export { BaseThemeProvider } from './base-provider.js';
|
|
15
|
+
export { CustomProvider, DEFAULT_TOKENS } from './custom-provider.js';
|
|
16
|
+
export { TailwindProvider } from './tailwind-provider.js';
|
|
17
|
+
export { ThemeEngine } from './theme-engine.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the stylesheets shipped with the package.
|
|
3
|
+
*
|
|
4
|
+
* @module @docpensieve/theme/styles
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Reads a stylesheet of the package.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} name File name, `'structure.css'` for instance.
|
|
10
|
+
* @returns {Promise<string>} Contents, trimmed.
|
|
11
|
+
* @throws {ThemeError} When the stylesheet is missing.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readStyle(name: string): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Joins several CSS fragments into one stylesheet.
|
|
16
|
+
*
|
|
17
|
+
* @param {...(string | undefined | null)} parts
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
export declare function joinCss(...parts: (string | undefined | null)[]): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme built on Tailwind CSS.
|
|
3
|
+
*
|
|
4
|
+
* @module @docpensieve/theme/tailwind-provider
|
|
5
|
+
*/
|
|
6
|
+
import { BaseThemeProvider } from './base-provider.js';
|
|
7
|
+
/** Tailwind theme: on-demand compilation of the classes actually used. */
|
|
8
|
+
export declare class TailwindProvider extends BaseThemeProvider {
|
|
9
|
+
#private;
|
|
10
|
+
tokens: {
|
|
11
|
+
'--dp-bg': "#ffffff";
|
|
12
|
+
'--dp-bg-soft': "oklch(96.8% 0.007 247.896)";
|
|
13
|
+
'--dp-text': "oklch(20.8% 0.042 265.755)";
|
|
14
|
+
'--dp-text-soft': "oklch(55.4% 0.046 257.417)";
|
|
15
|
+
'--dp-border': "oklch(92.9% 0.013 255.508)";
|
|
16
|
+
'--dp-rule': "oklch(92.9% 0.013 255.508)";
|
|
17
|
+
'--dp-accent': "oklch(51.1% 0.262 276.966)";
|
|
18
|
+
'--dp-accent-soft': "oklch(96.2% 0.018 272.314)";
|
|
19
|
+
'--dp-shadow': "rgba(15, 23, 42, 0.12)";
|
|
20
|
+
'--dp-radius': "0.375rem";
|
|
21
|
+
'--dp-font': "var(--font-sans, ui-sans-serif, system-ui, sans-serif)";
|
|
22
|
+
'--dp-font-mono': "var(--font-mono, ui-monospace, monospace)";
|
|
23
|
+
'--dp-content-width': "none";
|
|
24
|
+
'--dp-sidebar-width': "15.5rem";
|
|
25
|
+
'--dp-toc-width': "13rem";
|
|
26
|
+
};
|
|
27
|
+
extraCss: string;
|
|
28
|
+
source: string;
|
|
29
|
+
static id: string;
|
|
30
|
+
/**
|
|
31
|
+
* @param {{ tokens?: Record<string, string>, css?: string, source?: string }} [options]
|
|
32
|
+
* `source` replaces the entry stylesheet handed to Tailwind, to put a
|
|
33
|
+
* `@theme` block or additional directives in it.
|
|
34
|
+
*/
|
|
35
|
+
constructor(options?: {
|
|
36
|
+
tokens?: Record<string, string>;
|
|
37
|
+
css?: string;
|
|
38
|
+
source?: string;
|
|
39
|
+
});
|
|
40
|
+
/** @returns {Record<string, string>} Slots dressed in utilities. */
|
|
41
|
+
get classes(): Record<string, string>;
|
|
42
|
+
/**
|
|
43
|
+
* @param {import('./base-provider.js').CompileContext} [context]
|
|
44
|
+
* @returns {Promise<import('./base-provider.js').ThemeOutput>}
|
|
45
|
+
* @throws {ThemeError} When Tailwind is missing or its compilation fails.
|
|
46
|
+
*/
|
|
47
|
+
compile(context?: import('./base-provider.js').CompileContext): Promise<import('./base-provider.js').ThemeOutput>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registration and merging of theme providers.
|
|
3
|
+
*
|
|
4
|
+
* @module @docpensieve/theme/theme-engine
|
|
5
|
+
*/
|
|
6
|
+
import { BaseThemeProvider } from './base-provider.js';
|
|
7
|
+
/** Combines several providers into a single CSS output. */
|
|
8
|
+
export declare class ThemeEngine {
|
|
9
|
+
/** @type {Map<string, BaseThemeProvider>} */
|
|
10
|
+
providers: Map<string, BaseThemeProvider>;
|
|
11
|
+
constructor();
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} id Provider identifier.
|
|
14
|
+
* @param {BaseThemeProvider} provider
|
|
15
|
+
* @returns {this} To chain registrations.
|
|
16
|
+
*/
|
|
17
|
+
register(id: string, provider: BaseThemeProvider): this;
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} id
|
|
20
|
+
* @returns {BaseThemeProvider | undefined}
|
|
21
|
+
*/
|
|
22
|
+
get(id: string): BaseThemeProvider | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Shell class aliases, all providers combined.
|
|
25
|
+
*
|
|
26
|
+
* Synchronous and independent of `compile()`: templates need these classes
|
|
27
|
+
* to be rendered, whereas a utility provider needs the rendered pages to
|
|
28
|
+
* compile its CSS.
|
|
29
|
+
*
|
|
30
|
+
* @returns {Record<string, string>} Shared slots, overridden by each
|
|
31
|
+
* provider in registration order.
|
|
32
|
+
*/
|
|
33
|
+
get classes(): Record<string, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Merges the outputs of every registered provider.
|
|
36
|
+
*
|
|
37
|
+
* Registration order sets precedence: the last one registered wins on
|
|
38
|
+
* variables and class aliases, and its CSS is concatenated last, so it wins
|
|
39
|
+
* at equal specificity.
|
|
40
|
+
*
|
|
41
|
+
* The merged variables are emitted in a single `:root` block placed
|
|
42
|
+
* **before** the providers' CSS. That is what lets a provider override
|
|
43
|
+
* another's palette without duplicating its rules.
|
|
44
|
+
*
|
|
45
|
+
* @param {import('./base-provider.js').CompileContext} [context] Passed as
|
|
46
|
+
* is to every provider.
|
|
47
|
+
* @returns {Promise<import('./base-provider.js').ThemeOutput>}
|
|
48
|
+
* @throws {ThemeError} When no provider is registered.
|
|
49
|
+
*/
|
|
50
|
+
compile(context?: import('./base-provider.js').CompileContext): Promise<import('./base-provider.js').ThemeOutput>;
|
|
51
|
+
}
|