@tomehq/theme 0.3.1 → 0.3.3

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 (67) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{chunk-YXKONM3A.js → chunk-MSXVVBDW.js} +493 -143
  3. package/dist/entry.js +1 -1
  4. package/dist/index.d.ts +37 -1
  5. package/dist/index.js +1 -1
  6. package/package.json +5 -5
  7. package/src/Shell.test.tsx +405 -0
  8. package/src/Shell.tsx +248 -24
  9. package/src/__virtual_stubs/config.ts +2 -0
  10. package/src/__virtual_stubs/doc-context.ts +2 -0
  11. package/src/__virtual_stubs/overrides.ts +2 -0
  12. package/src/__virtual_stubs/page-loader.ts +4 -0
  13. package/src/__virtual_stubs/routes.ts +5 -0
  14. package/src/entry-helpers.test.ts +76 -0
  15. package/src/entry-helpers.ts +18 -1
  16. package/src/entry.test.tsx +695 -0
  17. package/src/entry.tsx +179 -4
  18. package/src/global.d.ts +11 -0
  19. package/vitest.config.ts +31 -1
  20. package/dist/chunk-2APCPR2Y.js +0 -2110
  21. package/dist/chunk-37JI6XGT.js +0 -1720
  22. package/dist/chunk-3A2LPGUL.js +0 -1991
  23. package/dist/chunk-3I2QTWTW.js +0 -1948
  24. package/dist/chunk-45M5UIAB.js +0 -2110
  25. package/dist/chunk-462AGU3S.js +0 -1959
  26. package/dist/chunk-7MUTU5D4.js +0 -1720
  27. package/dist/chunk-ABNPB6BB.js +0 -2133
  28. package/dist/chunk-BZGWSKT2.js +0 -573
  29. package/dist/chunk-CMQCNCSY.js +0 -2127
  30. package/dist/chunk-CTPOZMMK.js +0 -1703
  31. package/dist/chunk-DO544M3G.js +0 -1702
  32. package/dist/chunk-DPKZBFQP.js +0 -1777
  33. package/dist/chunk-EK7PZUEB.js +0 -2147
  34. package/dist/chunk-FMOLIHQF.js +0 -2182
  35. package/dist/chunk-FWBTK5TL.js +0 -1444
  36. package/dist/chunk-GDQIBNX5.js +0 -1962
  37. package/dist/chunk-GHQ2MODM.js +0 -2127
  38. package/dist/chunk-GR2WCRGK.js +0 -2182
  39. package/dist/chunk-HNLKDQ64.js +0 -2139
  40. package/dist/chunk-INUMUXN5.js +0 -2095
  41. package/dist/chunk-IW3NHNOQ.js +0 -2187
  42. package/dist/chunk-JA4PMX6M.js +0 -1500
  43. package/dist/chunk-JSPFS7G5.js +0 -2102
  44. package/dist/chunk-JZRT4WNC.js +0 -1441
  45. package/dist/chunk-KQBY2JDB.js +0 -2112
  46. package/dist/chunk-LIMYFTPC.js +0 -1468
  47. package/dist/chunk-MEP7P6A7.js +0 -1500
  48. package/dist/chunk-NOZBIES7.js +0 -1948
  49. package/dist/chunk-O4GH3KYX.js +0 -1712
  50. package/dist/chunk-OEXM3BEC.js +0 -1702
  51. package/dist/chunk-Q7PYTVW3.js +0 -1771
  52. package/dist/chunk-QCWZYABW.js +0 -1468
  53. package/dist/chunk-RDF25WB2.js +0 -2085
  54. package/dist/chunk-RKTT3ZEX.js +0 -1500
  55. package/dist/chunk-S47BRMNQ.js +0 -1715
  56. package/dist/chunk-S4ZH5F56.js +0 -1949
  57. package/dist/chunk-SRD7NJHS.js +0 -1949
  58. package/dist/chunk-SWFYJO5H.js +0 -2187
  59. package/dist/chunk-TQDWPSTO.js +0 -2087
  60. package/dist/chunk-TTRXRPP6.js +0 -1941
  61. package/dist/chunk-UKYFJSUA.js +0 -509
  62. package/dist/chunk-VKEQHP2E.js +0 -2133
  63. package/dist/chunk-VUT2FMSI.js +0 -1937
  64. package/dist/chunk-VVCC5JHK.js +0 -1949
  65. package/dist/chunk-W732TVBK.js +0 -1944
  66. package/dist/chunk-X4VQYPKO.js +0 -1768
  67. package/dist/chunk-YZ3P3TNS.js +0 -1760
package/src/entry.tsx CHANGED
@@ -13,11 +13,13 @@ import {
13
13
  // @ts-ignore — resolved by vite-plugin-tome
14
14
  import config from "virtual:tome/config";
15
15
  // @ts-ignore — resolved by vite-plugin-tome
16
- import { routes, navigation, versions } from "virtual:tome/routes";
16
+ import { routes, navigation, versions, i18n } from "virtual:tome/routes";
17
17
  // @ts-ignore — resolved by vite-plugin-tome
18
18
  import loadPageModule from "virtual:tome/page-loader";
19
19
  // @ts-ignore — resolved by vite-plugin-tome
20
20
  import docContext from "virtual:tome/doc-context";
21
+ // @ts-ignore — resolved by vite-plugin-tome
22
+ import overrides from "virtual:tome/overrides";
21
23
 
22
24
  // TOM-8: Built-in MDX components from @tomehq/components
23
25
  // These are injected into every MDX page automatically
@@ -32,6 +34,10 @@ import {
32
34
  PackageManager,
33
35
  TypeTable,
34
36
  FileTree,
37
+ CodeSamples,
38
+ LinkCard,
39
+ CardGrid,
40
+ ApiReference,
35
41
  } from "@tomehq/components";
36
42
 
37
43
  const MDX_COMPONENTS: Record<string, React.ComponentType<any>> = {
@@ -45,6 +51,9 @@ const MDX_COMPONENTS: Record<string, React.ComponentType<any>> = {
45
51
  PackageManager,
46
52
  TypeTable,
47
53
  FileTree, // Sub-components accessible as <FileTree.File /> and <FileTree.Folder /> in MDX
54
+ CodeSamples,
55
+ LinkCard,
56
+ CardGrid,
48
57
  };
49
58
 
50
59
  // ── CONTENT STYLES ───────────────────────────────────────
@@ -60,15 +69,15 @@ const contentStyles = `
60
69
  .tome-content a { color: var(--ac); text-decoration: none; }
61
70
  .tome-content a:hover { text-decoration: underline; }
62
71
  .tome-content .heading-anchor { display: none; }
63
- .tome-content ul, .tome-content ol { color: var(--tx2); padding-left: 1.5em; margin-bottom: 1em; }
72
+ .tome-content ul, .tome-content ol { color: var(--tx2); padding-inline-start: 1.5em; margin-bottom: 1em; }
64
73
  .tome-content li { margin-bottom: 0.3em; line-height: 1.7; }
65
74
  .tome-content code { font-family: var(--font-code); font-size: 0.88em; background: var(--cdBg); padding: 0.15em 0.4em; border-radius: 2px; color: var(--ac); }
66
75
  .tome-content pre { margin-bottom: 1.2em; border-radius: 2px; overflow-x: auto; border: 1px solid var(--bd); }
67
76
  .tome-content pre code { background: none; padding: 1em 1.2em; display: block; font-size: 12.5px; line-height: 1.7; color: var(--cdTx); }
68
- .tome-content blockquote { border-left: 3px solid var(--ac); padding: 0.5em 1em; margin: 1em 0; background: var(--acD); border-radius: 0 2px 2px 0; }
77
+ .tome-content blockquote { border-inline-start: 3px solid var(--ac); padding: 0.5em 1em; margin: 1em 0; background: var(--acD); border-radius: 0 2px 2px 0; }
69
78
  .tome-content blockquote p { color: var(--tx2); margin: 0; }
70
79
  .tome-content table { width: 100%; border-collapse: collapse; margin-bottom: 1em; }
71
- .tome-content th, .tome-content td { padding: 0.5em 0.8em; border: 1px solid var(--bd); text-align: left; font-size: 0.9em; }
80
+ .tome-content th, .tome-content td { padding: 0.5em 0.8em; border: 1px solid var(--bd); text-align: start; font-size: 0.9em; }
72
81
  .tome-content th { background: var(--sf); font-weight: 600; }
73
82
  .tome-content img { max-width: 100%; border-radius: 2px; cursor: zoom-in; }
74
83
  .tome-content hr { border: none; border-top: 1px solid var(--bd); margin: 2em 0; }
@@ -122,6 +131,74 @@ const contentStyles = `
122
131
  background-repeat: repeat; background-size: 256px;
123
132
  }
124
133
 
134
+ /* ── Expressive code blocks ───────────────────────────── */
135
+
136
+ /* Code block wrapper (for titled blocks) */
137
+ .tome-code-block-wrapper { position: relative; margin-bottom: 1.2em; border: 1px solid var(--bd); border-radius: 2px; overflow: hidden; }
138
+ .tome-code-block-wrapper pre { margin-bottom: 0; border: none; border-radius: 0; }
139
+ .tome-code-title {
140
+ font-family: var(--font-code); font-size: 12px; color: var(--tx2);
141
+ background: var(--sf); padding: 6px 12px; border-bottom: 1px solid var(--bd);
142
+ letter-spacing: 0.01em; font-weight: 500;
143
+ }
144
+
145
+ /* Line highlighting */
146
+ .tome-content pre .line.tome-line-highlight {
147
+ background: rgba(139, 148, 158, 0.1);
148
+ display: inline-block; width: 100%; margin: 0 -1.2em; padding: 0 1.2em;
149
+ }
150
+ html.dark .tome-content pre .line.tome-line-highlight {
151
+ background: rgba(200, 210, 220, 0.08);
152
+ }
153
+
154
+ /* Diff lines */
155
+ .tome-content pre .line.tome-line-added {
156
+ background: rgba(34, 197, 94, 0.12);
157
+ display: inline-block; width: 100%; margin: 0 -1.2em; padding: 0 1.2em;
158
+ }
159
+ .tome-content pre .line.tome-line-removed {
160
+ background: rgba(239, 68, 68, 0.12);
161
+ display: inline-block; width: 100%; margin: 0 -1.2em; padding: 0 1.2em;
162
+ }
163
+ html.dark .tome-content pre .line.tome-line-added { background: rgba(34, 197, 94, 0.15); }
164
+ html.dark .tome-content pre .line.tome-line-removed { background: rgba(239, 68, 68, 0.15); }
165
+
166
+ /* Line numbers (CSS counter) */
167
+ .tome-content pre[data-line-numbers] code {
168
+ counter-reset: line;
169
+ }
170
+ .tome-content pre[data-line-numbers] .line::before {
171
+ counter-increment: line;
172
+ content: counter(line);
173
+ display: inline-block; width: 2.5em; margin-inline-end: 1em;
174
+ text-align: end; color: var(--txM); opacity: 0.4;
175
+ font-size: 0.85em; user-select: none;
176
+ border-inline-end: 1px solid var(--bd); padding-inline-end: 0.8em; margin-inline-end: 0.8em;
177
+ }
178
+
179
+ /* Word highlighting */
180
+ .tome-word-highlight {
181
+ background: rgba(139, 148, 158, 0.2); border-radius: 2px;
182
+ padding: 1px 3px; margin: 0 -1px;
183
+ }
184
+ html.dark .tome-word-highlight {
185
+ background: rgba(200, 210, 220, 0.15);
186
+ }
187
+
188
+ /* Copy button */
189
+ .tome-content pre { position: relative; }
190
+ .tome-copy-btn {
191
+ position: absolute; top: 8px; inset-inline-end: 8px;
192
+ font-family: var(--font-code); font-size: 11px;
193
+ color: var(--tx2); background: var(--sf); border: 1px solid var(--bd);
194
+ padding: 3px 8px; border-radius: 2px; cursor: pointer;
195
+ opacity: 0; transition: opacity 0.15s;
196
+ z-index: 2; line-height: 1.4;
197
+ }
198
+ .tome-content pre:hover .tome-copy-btn,
199
+ .tome-copy-btn:focus { opacity: 1; }
200
+ .tome-copy-btn:hover { background: var(--sfH); }
201
+
125
202
  /* Shiki dual-theme support */
126
203
  .shiki { background: var(--cdBg) !important; }
127
204
 
@@ -143,6 +220,57 @@ const contentStyles = `
143
220
  html:not(.dark) .shiki span[style*="color:#22863A"] { color: #1a6e2e !important; }
144
221
  html:not(.dark) .shiki span[style*="color:#D73A49"] { color: #b62324 !important; }
145
222
  html:not(.dark) .shiki span[style*="color:#005CC5"] { color: #0349b4 !important; }
223
+
224
+ /* ── Twoslash type hover tooltips ───────────────────── */
225
+ .twoslash-hover {
226
+ position: relative;
227
+ border-bottom: 1px dotted var(--tx2);
228
+ cursor: help;
229
+ }
230
+ .twoslash-popup-container {
231
+ position: absolute;
232
+ opacity: 0;
233
+ display: none;
234
+ z-index: 10;
235
+ left: 0;
236
+ top: 100%;
237
+ margin-top: 4px;
238
+ padding: 6px 10px;
239
+ background: var(--sf);
240
+ border: 1px solid var(--bd);
241
+ border-radius: 6px;
242
+ font-size: 12px;
243
+ font-family: var(--font-code);
244
+ color: var(--tx);
245
+ white-space: pre-wrap;
246
+ max-width: 500px;
247
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
248
+ pointer-events: none;
249
+ }
250
+ .twoslash-hover:hover .twoslash-popup-container {
251
+ opacity: 1;
252
+ display: block;
253
+ }
254
+ /* Twoslash error/warning underlines */
255
+ .twoslash-error {
256
+ position: relative;
257
+ background: rgba(239, 68, 68, 0.1);
258
+ border-bottom: 2px wavy rgba(239, 68, 68, 0.6);
259
+ }
260
+ /* Twoslash highlighted identifiers */
261
+ .twoslash-highlighted {
262
+ background: rgba(139, 148, 158, 0.15);
263
+ border-radius: 2px;
264
+ padding: 1px 2px;
265
+ }
266
+ /* Twoslash type annotation line (^?) */
267
+ .twoslash-popup-code .shiki { background: transparent !important; padding: 0; margin: 0; }
268
+ .twoslash-popup-code .shiki code { padding: 0; font-size: 12px; }
269
+ html.dark .twoslash-popup-container {
270
+ background: var(--sf);
271
+ border-color: var(--bd);
272
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
273
+ }
146
274
  `;
147
275
 
148
276
  // ── ROUTING HELPERS ──────────────────────────────────────
@@ -309,6 +437,39 @@ function App() {
309
437
  return () => { cancelled = true; };
310
438
  }, [pageData, loading, mermaidTheme]);
311
439
 
440
+ // Add copy buttons to all pre blocks (expressive code blocks)
441
+ useEffect(() => {
442
+ if (loading) return;
443
+ const preBlocks = document.querySelectorAll(".tome-content pre");
444
+ const buttons: HTMLButtonElement[] = [];
445
+ preBlocks.forEach((pre) => {
446
+ // Skip if already has a copy button
447
+ if (pre.querySelector(".tome-copy-btn")) return;
448
+ const btn = document.createElement("button");
449
+ btn.className = "tome-copy-btn";
450
+ btn.textContent = "Copy";
451
+ btn.addEventListener("click", async () => {
452
+ const code = pre.querySelector("code");
453
+ if (code) {
454
+ try {
455
+ await navigator.clipboard.writeText(code.textContent || "");
456
+ btn.textContent = "Copied!";
457
+ setTimeout(() => { btn.textContent = "Copy"; }, 2000);
458
+ } catch {
459
+ // Fallback for non-HTTPS contexts
460
+ btn.textContent = "Failed";
461
+ setTimeout(() => { btn.textContent = "Copy"; }, 2000);
462
+ }
463
+ }
464
+ });
465
+ pre.appendChild(btn);
466
+ buttons.push(btn);
467
+ });
468
+ return () => {
469
+ buttons.forEach((btn) => btn.remove());
470
+ };
471
+ }, [pageData, loading]);
472
+
312
473
  const allPages = routes.map((r: any) => ({
313
474
  id: r.id,
314
475
  title: r.frontmatter.title,
@@ -320,6 +481,10 @@ function App() {
320
481
  const currentVersion = detectCurrentVersion(currentRoute, versions);
321
482
  const editUrl = computeEditUrl(config.editLink, currentRoute?.filePath);
322
483
 
484
+ // RTL: detect current locale and compute text direction
485
+ const currentLocale = currentRoute?.locale || i18n?.defaultLocale || "en";
486
+ const dir: "ltr" | "rtl" = i18n?.localeDirs?.[currentLocale] || "ltr";
487
+
323
488
  // KaTeX CSS: inject stylesheet when math is enabled or math placeholders exist
324
489
  useEffect(() => {
325
490
  const hasMathPlaceholders = document.querySelectorAll(".tome-math[data-math]").length > 0;
@@ -385,12 +550,22 @@ function App() {
385
550
  editUrl={editUrl}
386
551
  lastUpdated={currentRoute?.lastUpdated}
387
552
  changelogEntries={!pageData?.isMdx ? pageData?.changelogEntries : undefined}
553
+ apiManifest={(!pageData?.isMdx && pageData?.isApiReference) ? pageData.apiManifest : undefined}
554
+ apiBaseUrl={config.api?.baseUrl}
555
+ apiPlayground={config.api?.playground}
556
+ apiAuth={config.api?.auth}
557
+ ApiReferenceComponent={ApiReference}
388
558
  onNavigate={navigateTo}
389
559
  allPages={allPages}
390
560
  docContext={docContext}
391
561
  versioning={versions || undefined}
392
562
  currentVersion={currentVersion}
393
563
  basePath={basePath}
564
+ isDraft={currentRoute?.frontmatter?.draft === true}
565
+ dir={dir}
566
+ i18n={i18n || undefined}
567
+ currentLocale={currentLocale}
568
+ overrides={overrides}
394
569
  />
395
570
  </>
396
571
  );
package/src/global.d.ts CHANGED
@@ -21,3 +21,14 @@ declare module "virtual:tome/doc-context" {
21
21
  const docContext: Array<{ id: string; title: string; content: string }>;
22
22
  export default docContext;
23
23
  }
24
+
25
+ declare module "virtual:tome/overrides" {
26
+ const overrides: {
27
+ Header?: React.ComponentType<any>;
28
+ Footer?: React.ComponentType<any>;
29
+ Sidebar?: React.ComponentType<any>;
30
+ Toc?: React.ComponentType<any>;
31
+ PageFooter?: React.ComponentType<any>;
32
+ };
33
+ export default overrides;
34
+ }
package/vitest.config.ts CHANGED
@@ -1,17 +1,47 @@
1
- import { defineConfig } from "vitest/config";
1
+ import { defineConfig, type Plugin } from "vitest/config";
2
2
  import { resolve } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
 
5
5
  const __dirname = fileURLToPath(new URL(".", import.meta.url));
6
6
 
7
+ // Vite plugin that resolves virtual:tome/* imports to real files on disk.
8
+ // In production, these are resolved by vite-plugin-tome; in tests we need
9
+ // concrete files so that vi.mock can intercept them.
10
+ function virtualTomeStubs(): Plugin {
11
+ const stubs: Record<string, string> = {
12
+ "virtual:tome/config": resolve(__dirname, "src/__virtual_stubs/config.ts"),
13
+ "virtual:tome/routes": resolve(__dirname, "src/__virtual_stubs/routes.ts"),
14
+ "virtual:tome/page-loader": resolve(__dirname, "src/__virtual_stubs/page-loader.ts"),
15
+ "virtual:tome/doc-context": resolve(__dirname, "src/__virtual_stubs/doc-context.ts"),
16
+ "virtual:tome/overrides": resolve(__dirname, "src/__virtual_stubs/overrides.ts"),
17
+ };
18
+
19
+ return {
20
+ name: "virtual-tome-stubs",
21
+ enforce: "pre",
22
+ resolveId(id) {
23
+ const resolved = stubs[id];
24
+ if (resolved) return resolved;
25
+ return null;
26
+ },
27
+ };
28
+ }
29
+
7
30
  export default defineConfig({
8
31
  root: __dirname,
32
+ plugins: [virtualTomeStubs()],
9
33
  test: {
10
34
  name: "theme",
11
35
  environment: "jsdom",
12
36
  globals: true,
13
37
  include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
14
38
  setupFiles: [resolve(__dirname, "src/test-setup.ts")],
39
+ server: {
40
+ deps: {
41
+ // Force Vite to transform entry.tsx and its virtual imports inline
42
+ inline: [/virtual:tome/],
43
+ },
44
+ },
15
45
  coverage: {
16
46
  provider: "v8",
17
47
  include: ["src/**/*.tsx", "src/**/*.ts"],