@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.
@@ -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
+ }
@@ -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
+ }