duckfn-docs-kit 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.
Files changed (65) hide show
  1. package/AGENTS.md +689 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/dom.d.ts +69 -0
  5. package/dist/home/DfkFeatures.d.ts +20 -0
  6. package/dist/home/DfkHero.d.ts +25 -0
  7. package/dist/home/DfkNextSteps.d.ts +16 -0
  8. package/dist/home/styles.d.ts +8 -0
  9. package/dist/index.d.ts +50 -0
  10. package/dist/index.js +2 -0
  11. package/dist/register-DKLiYs-F.js +2324 -0
  12. package/dist/register.d.ts +10 -0
  13. package/dist/remark.d.ts +21 -0
  14. package/dist/remark.js +15 -0
  15. package/dist/runtimeConfig-Bokbb8VH.js +106 -0
  16. package/dist/sql/DfkSql.d.ts +7 -0
  17. package/dist/sql/PreviewTabs.d.ts +37 -0
  18. package/dist/sql/client.d.ts +1 -0
  19. package/dist/sql/client.js +4 -0
  20. package/dist/sql/editor.d.ts +16 -0
  21. package/dist/sql/extensions.d.ts +108 -0
  22. package/dist/sql/extensions.js +198 -0
  23. package/dist/sql/remark.d.ts +88 -0
  24. package/dist/sql/remark.js +69 -0
  25. package/dist/sql/renderers.d.ts +44 -0
  26. package/dist/sql/runtime.d.ts +105 -0
  27. package/dist/sql/runtimeConfig.d.ts +80 -0
  28. package/dist/sql/styles.d.ts +6 -0
  29. package/dist/toc-toggle/TocToggle.d.ts +46 -0
  30. package/dist/toc-toggle/TocToggle.js +69 -0
  31. package/dist/toc-toggle/client.d.ts +1 -0
  32. package/dist/toc-toggle/client.js +9 -0
  33. package/dist/toc-toggle/plugin.d.ts +36 -0
  34. package/dist/toc-toggle/plugin.js +13 -0
  35. package/dist/types.d.ts +42 -0
  36. package/package.json +73 -0
  37. package/src/dom.ts +109 -0
  38. package/src/home/DfkFeatures.ts +78 -0
  39. package/src/home/DfkHero.ts +128 -0
  40. package/src/home/DfkNextSteps.ts +73 -0
  41. package/src/home/home.css +520 -0
  42. package/src/home/styles.ts +28 -0
  43. package/src/index.ts +59 -0
  44. package/src/kit.css +19 -0
  45. package/src/register.ts +39 -0
  46. package/src/remark.ts +60 -0
  47. package/src/sql/DfkSql.css +226 -0
  48. package/src/sql/DfkSql.ts +620 -0
  49. package/src/sql/PreviewTabs.ts +169 -0
  50. package/src/sql/client.ts +16 -0
  51. package/src/sql/editor.ts +75 -0
  52. package/src/sql/extensions.ts +470 -0
  53. package/src/sql/remark.ts +213 -0
  54. package/src/sql/renderers.ts +916 -0
  55. package/src/sql/runtime.ts +348 -0
  56. package/src/sql/runtimeConfig.ts +249 -0
  57. package/src/sql/sql.css +397 -0
  58. package/src/sql/styles.ts +24 -0
  59. package/src/theme/tokens.css +75 -0
  60. package/src/toc-toggle/TocToggle.css +69 -0
  61. package/src/toc-toggle/TocToggle.ts +172 -0
  62. package/src/toc-toggle/client.ts +20 -0
  63. package/src/toc-toggle/plugin.ts +54 -0
  64. package/src/types.ts +47 -0
  65. package/src/vite-env.d.ts +8 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Adds a collapse/expand control to the desktop table of contents.
3
+ *
4
+ * Docusaurus only has `themeConfig.docs.sidebar.hideable` for the left sidebar;
5
+ * the right-hand TOC has no such option (`themeConfig.tableOfContents` only
6
+ * accepts `minHeadingLevel` / `maxHeadingLevel`). So the button is injected here
7
+ * and the layout is switched by the `toc-collapsed` class on `<body>`. The
8
+ * matching CSS lives in `TocToggle.css`, next to this file.
9
+ *
10
+ * This is the class-ified form of the original TOC glue: the button and TOC
11
+ * references are held in fields instead of being looked up with
12
+ * `document.querySelector` on every update, and all module-level mutable state
13
+ * now lives inside {@link TocToggle}. `client.ts` next to this file is the thin
14
+ * Docusaurus glue that the `toc-toggle/plugin` entry injects into a site.
15
+ */
16
+
17
+ const DEFAULT_STORAGE_KEY = 'duckfn:toc-collapsed';
18
+ const STATE_CLASS = 'toc-collapsed';
19
+ const BUTTON_CLASS = 'toc-toggle';
20
+ const COLUMN_CLASS = 'toc-column';
21
+ const TOC_ID = 'doc-toc';
22
+ const TOC_SELECTOR = '.theme-doc-toc-desktop';
23
+ const DESKTOP_QUERY = '(min-width: 997px)';
24
+
25
+ export interface TocToggleLabels {
26
+ hide: string;
27
+ show: string;
28
+ }
29
+
30
+ export interface TocToggleOptions {
31
+ /** Keyed by a lower-cased html-lang prefix; falls back to `en`. */
32
+ labels?: Record<string, TocToggleLabels>;
33
+ storageKey?: string;
34
+ }
35
+
36
+ const DEFAULT_LABELS: Record<string, TocToggleLabels> = {
37
+ en: {hide: 'Collapse table of contents', show: 'Expand table of contents'},
38
+ 'zh-hans': {hide: '收起目录', show: '展开目录'},
39
+ };
40
+
41
+ export class TocToggle {
42
+ readonly #labels: Record<string, TocToggleLabels>;
43
+ readonly #storageKey: string;
44
+ #collapsed = false;
45
+ #button: HTMLButtonElement | null = null;
46
+
47
+ constructor(options: TocToggleOptions = {}) {
48
+ this.#labels = options.labels ?? DEFAULT_LABELS;
49
+ this.#storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
50
+ }
51
+
52
+ #currentLabels(): TocToggleLabels {
53
+ const lang = (document.documentElement.getAttribute('lang') ?? 'en').toLowerCase();
54
+ return this.#labels[lang] ?? this.#labels.en ?? DEFAULT_LABELS.en;
55
+ }
56
+
57
+ #readPreference(): boolean {
58
+ try {
59
+ return window.localStorage.getItem(this.#storageKey) === 'true';
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ #storePreference(value: boolean): void {
66
+ try {
67
+ window.localStorage.setItem(this.#storageKey, String(value));
68
+ } catch {
69
+ // Storage may be unavailable (private mode, blocked cookies). The toggle
70
+ // still works for the current page; the choice is just not remembered.
71
+ }
72
+ }
73
+
74
+ #updateLabel(): void {
75
+ if (!this.#button) {
76
+ return;
77
+ }
78
+ const {hide, show} = this.#currentLabels();
79
+ const label = this.#collapsed ? show : hide;
80
+ this.#button.setAttribute('aria-label', label);
81
+ this.#button.setAttribute('title', label);
82
+ this.#button.setAttribute('aria-expanded', String(!this.#collapsed));
83
+ }
84
+
85
+ #createButton(toc: Element): HTMLButtonElement {
86
+ if (!toc.id) {
87
+ toc.id = TOC_ID;
88
+ }
89
+
90
+ const button = document.createElement('button');
91
+ button.type = 'button';
92
+ button.className = `clean-btn ${BUTTON_CLASS}`;
93
+ button.setAttribute('aria-controls', toc.id);
94
+ button.addEventListener('click', () => {
95
+ this.#collapsed = !this.#collapsed;
96
+ this.#storePreference(this.#collapsed);
97
+ this.#apply();
98
+ });
99
+ return button;
100
+ }
101
+
102
+ /**
103
+ * The state class goes on `<body>`, not on `<html>`: Docusaurus rewrites the
104
+ * whole `class` attribute of `<html>` on every route, which would drop the
105
+ * class immediately after it is set. `document.body` is left alone.
106
+ */
107
+ #setStateClass(value: boolean): void {
108
+ document.body?.classList.toggle(STATE_CLASS, value);
109
+ }
110
+
111
+ #apply(): void {
112
+ this.#setStateClass(this.#collapsed);
113
+ this.#updateLabel();
114
+ }
115
+
116
+ /**
117
+ * Reconciles the button with the current page. The TOC is rendered by React,
118
+ * so it only exists on pages with headings and only on wide viewports; the
119
+ * button may also have been discarded by a re-render, so it is rebuilt when
120
+ * missing.
121
+ */
122
+ refresh(): void {
123
+ const toc = document.querySelector(TOC_SELECTOR);
124
+ // The field may point at a button React has since removed from the DOM.
125
+ if (this.#button && !this.#button.isConnected) {
126
+ this.#button = null;
127
+ }
128
+
129
+ if (!toc) {
130
+ // No desktop TOC on this page: drop the button and the layout class, so
131
+ // the article always uses the full width where there is nothing to
132
+ // collapse.
133
+ this.#button?.remove();
134
+ this.#button = null;
135
+ this.#setStateClass(false);
136
+ return;
137
+ }
138
+
139
+ toc.parentElement?.classList.add(COLUMN_CLASS);
140
+
141
+ if (!this.#button) {
142
+ this.#button = this.#createButton(toc);
143
+ toc.parentElement?.insertBefore(this.#button, toc);
144
+ }
145
+
146
+ this.#apply();
147
+ }
148
+
149
+ /**
150
+ * Reads the stored preference, applies it before React renders (so a collapsed
151
+ * TOC never flashes open) and wires up the viewport listener.
152
+ */
153
+ init(): void {
154
+ this.#collapsed = this.#readPreference();
155
+ this.#setStateClass(this.#collapsed);
156
+
157
+ // React drops the desktop TOC when the viewport shrinks; re-check once it
158
+ // has re-rendered.
159
+ window
160
+ .matchMedia(DESKTOP_QUERY)
161
+ .addEventListener('change', () => window.setTimeout(() => this.refresh(), 0));
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Builds a {@link TocToggle}. `client.ts` — the glue the `toc-toggle/plugin`
167
+ * entry injects — calls `init()` once (behind a `typeof window` guard) and
168
+ * exports `onRouteDidUpdate` bound to `refresh()`.
169
+ */
170
+ export function createTocToggle(options?: TocToggleOptions): TocToggle {
171
+ return new TocToggle(options);
172
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The TOC toggle's browser glue, injected by `toc-toggle/plugin` so a
3
+ * consuming site does not keep a client module of its own. The behaviour lives
4
+ * in `toc-toggle/TocToggle`; this module is only the Docusaurus lifecycle
5
+ * glue: initialise once in the browser and refresh after every route change.
6
+ *
7
+ * Docusaurus evaluates this module in its Node prerender pass too, where there
8
+ * is no DOM to touch — hence the guard.
9
+ */
10
+ import {createTocToggle} from './TocToggle';
11
+
12
+ const toggle = createTocToggle();
13
+
14
+ if (typeof window !== 'undefined') {
15
+ toggle.init();
16
+ }
17
+
18
+ export function onRouteDidUpdate(): void {
19
+ toggle.refresh();
20
+ }
@@ -0,0 +1,54 @@
1
+ import {createRequire} from 'node:module';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * `duckfn-docs-kit/toc-toggle/plugin` — wires the kit's TOC collapse control
6
+ * into a Docusaurus site.
7
+ *
8
+ * The button and its behaviour live in `toc-toggle/TocToggle` +
9
+ * `TocToggle.css`; what a site used to hand-write as a client module is now
10
+ * injected by this plugin, so a site's own `clientModules` only carries client
11
+ * code the site itself owns.
12
+ *
13
+ * This is Node-side build code: it must not import any browser module, and the
14
+ * browser side must not import this file (the glue it injects is
15
+ * `./client.ts`, resolved through the package exports map).
16
+ */
17
+
18
+ /**
19
+ * The slice of Docusaurus' plugin API this module touches, typed structurally
20
+ * (like `sql/extensions.ts`) so the kit keeps zero Docusaurus dependencies.
21
+ */
22
+ export interface DfkTocToggleContext {
23
+ siteDir: string;
24
+ }
25
+
26
+ export interface DfkTocTogglePlugin {
27
+ name: string;
28
+ getClientModules(): string[];
29
+ }
30
+
31
+ /** The plugin module Docusaurus calls with its `LoadContext` and options. */
32
+ export type DfkTocTogglePluginModule = (context: DfkTocToggleContext) => DfkTocTogglePlugin;
33
+
34
+ /**
35
+ * Builds the plugin. Usage in `docusaurus.config.ts`:
36
+ *
37
+ * ```ts
38
+ * import {dfkTocToggle} from 'duckfn-docs-kit/toc-toggle/plugin';
39
+ *
40
+ * plugins: [dfkTocToggle()],
41
+ * ```
42
+ */
43
+ export function dfkTocToggle(): DfkTocTogglePluginModule {
44
+ return (context) => ({
45
+ name: 'dfk-toc-toggle',
46
+ getClientModules() {
47
+ // Resolved from the consuming site, so the config bundler cannot break
48
+ // the lookup; the exports map (`./*` -> dist) keeps the subpath valid
49
+ // even if the entry moves.
50
+ const requireFromSite = createRequire(path.join(context.siteDir, 'package.json'));
51
+ return [requireFromSite.resolve('duckfn-docs-kit/toc-toggle/client')];
52
+ },
53
+ });
54
+ }
package/src/types.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Value types the home-page web components accept.
3
+ *
4
+ * Every string is already resolved for the *current* locale: a custom element
5
+ * cannot render Docusaurus' React `<Translate>`, so the docs site resolves the
6
+ * copy with the imperative `translate()` API and hands plain strings to the
7
+ * components' `set*` methods. Internal `href`s are baseUrl-resolved by the
8
+ * caller (`useBaseUrl`), because a raw `<a href>` inside a custom element gets
9
+ * no Docusaurus prefixing.
10
+ *
11
+ * Icon fields are Iconify icon *names* (e.g. `lucide:arrow-right`), rendered by
12
+ * the official `<iconify-icon>` web component, which fetches the glyph from the
13
+ * public Iconify API. This package ships no icon data of its own.
14
+ */
15
+
16
+ /** A hero call-to-action. */
17
+ export interface HeroLink {
18
+ label: string;
19
+ href: string;
20
+ }
21
+
22
+ /** The secondary hero action (the GitHub button), which also carries a glyph. */
23
+ export interface HeroAction extends HeroLink {
24
+ /** Iconify icon name, e.g. `simple-icons:github`. */
25
+ icon: string;
26
+ }
27
+
28
+ /** A shields.io-style badge in the hero row. Always an external link. */
29
+ export interface HeroBadge {
30
+ href: string;
31
+ src: string;
32
+ alt: string;
33
+ }
34
+
35
+ export interface FeatureItem {
36
+ /** Iconify icon name, e.g. `lucide:sparkles`. */
37
+ icon: string;
38
+ title: string;
39
+ details: string;
40
+ }
41
+
42
+ export interface NextStepItem {
43
+ /** Already baseUrl-resolved internal path. */
44
+ href: string;
45
+ title: string;
46
+ details: string;
47
+ }
@@ -0,0 +1,8 @@
1
+ // Ambient module type for the Vite `?inline` CSS import in `styles.ts`. Vite
2
+ // injects this file into the build automatically; `tsc` picks it up through the
3
+ // `include: ["src"]` glob, which is what keeps `npm run typecheck` and the
4
+ // emitted `.d.ts` files working without a `vite/client` lib reference.
5
+ declare module '*.css?inline' {
6
+ const css: string;
7
+ export default css;
8
+ }