toiljs 0.0.11 → 0.0.14

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 (120) hide show
  1. package/README.md +3 -1
  2. package/build/cli/.tsbuildinfo +1 -1
  3. package/build/cli/configure.js +10 -4
  4. package/build/cli/create.js +58 -30
  5. package/build/cli/diagnostics.d.ts +55 -0
  6. package/build/cli/diagnostics.js +333 -0
  7. package/build/cli/doctor.d.ts +6 -0
  8. package/build/cli/doctor.js +249 -0
  9. package/build/cli/index.js +26 -0
  10. package/build/cli/proc.d.ts +5 -0
  11. package/build/cli/proc.js +20 -0
  12. package/build/cli/ui.d.ts +1 -0
  13. package/build/cli/ui.js +1 -0
  14. package/build/cli/update.d.ts +7 -0
  15. package/build/cli/update.js +117 -0
  16. package/build/cli/updates.d.ts +10 -0
  17. package/build/cli/updates.js +45 -0
  18. package/build/client/.tsbuildinfo +1 -1
  19. package/build/client/dev/error-overlay.js +1 -1
  20. package/build/client/head/metadata.js +3 -1
  21. package/build/client/index.d.ts +5 -1
  22. package/build/client/index.js +2 -0
  23. package/build/client/navigation/navigation.js +1 -1
  24. package/build/client/routing/Router.js +2 -2
  25. package/build/client/search/search.d.ts +26 -0
  26. package/build/client/search/search.js +101 -0
  27. package/build/client/search/use-page-search.d.ts +8 -0
  28. package/build/client/search/use-page-search.js +21 -0
  29. package/build/compiler/.tsbuildinfo +1 -1
  30. package/build/compiler/generate.js +33 -24
  31. package/build/compiler/index.d.ts +2 -0
  32. package/build/compiler/index.js +1 -0
  33. package/build/compiler/pages.d.ts +8 -0
  34. package/build/compiler/pages.js +37 -0
  35. package/build/compiler/plugin.js +3 -1
  36. package/build/compiler/prerender.d.ts +1 -0
  37. package/build/compiler/prerender.js +11 -5
  38. package/build/compiler/seo.js +10 -3
  39. package/build/io/.tsbuildinfo +1 -1
  40. package/examples/basic/client/components/Header.tsx +43 -41
  41. package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
  42. package/examples/basic/client/public/index.html +18 -16
  43. package/examples/basic/client/routes/(legal)/privacy.tsx +18 -19
  44. package/examples/basic/client/routes/(legal)/terms.tsx +15 -16
  45. package/examples/basic/client/routes/about.tsx +21 -22
  46. package/examples/basic/client/routes/blog/[id].tsx +26 -18
  47. package/examples/basic/client/routes/features/actions.tsx +67 -67
  48. package/examples/basic/client/routes/features/error/index.tsx +27 -27
  49. package/examples/basic/client/routes/features/head.tsx +38 -38
  50. package/examples/basic/client/routes/features/index.tsx +83 -75
  51. package/examples/basic/client/routes/features/realtime.tsx +34 -32
  52. package/examples/basic/client/routes/features/script.tsx +31 -31
  53. package/examples/basic/client/routes/features/seo.tsx +39 -39
  54. package/examples/basic/client/routes/features/template/index.tsx +20 -20
  55. package/examples/basic/client/routes/features/template/template.tsx +16 -18
  56. package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -23
  57. package/examples/basic/client/routes/gallery/index.tsx +42 -42
  58. package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -18
  59. package/examples/basic/client/routes/get-started.tsx +157 -84
  60. package/examples/basic/client/routes/index.tsx +137 -96
  61. package/examples/basic/client/routes/loader-demo/index.tsx +59 -52
  62. package/examples/basic/client/routes/search.tsx +61 -0
  63. package/examples/basic/client/routes/test.tsx +7 -8
  64. package/examples/basic/client/styles/main.css +624 -552
  65. package/package.json +2 -2
  66. package/presets/eslint.js +10 -3
  67. package/src/cli/configure.ts +363 -353
  68. package/src/cli/create.ts +563 -530
  69. package/src/cli/diagnostics.ts +421 -0
  70. package/src/cli/doctor.ts +318 -0
  71. package/src/cli/features.ts +166 -160
  72. package/src/cli/index.ts +242 -211
  73. package/src/cli/proc.ts +30 -0
  74. package/src/cli/ui.ts +111 -103
  75. package/src/cli/update.ts +150 -0
  76. package/src/cli/updates.ts +69 -0
  77. package/src/client/components/Image.tsx +91 -89
  78. package/src/client/dev/error-overlay.tsx +193 -197
  79. package/src/client/head/metadata.ts +94 -92
  80. package/src/client/index.ts +79 -64
  81. package/src/client/navigation/Link.tsx +94 -100
  82. package/src/client/navigation/navigation.ts +215 -218
  83. package/src/client/routing/Router.tsx +210 -193
  84. package/src/client/routing/hooks.ts +110 -114
  85. package/src/client/routing/lazy.ts +77 -81
  86. package/src/client/search/search.ts +189 -0
  87. package/src/client/search/use-page-search.ts +73 -0
  88. package/src/compiler/config.ts +173 -171
  89. package/src/compiler/fonts.ts +89 -87
  90. package/src/compiler/generate.ts +45 -27
  91. package/src/compiler/image-report.ts +88 -85
  92. package/src/compiler/index.ts +2 -0
  93. package/src/compiler/pages.ts +70 -0
  94. package/src/compiler/plugin.ts +51 -47
  95. package/src/compiler/prerender.ts +152 -130
  96. package/src/compiler/routes.ts +132 -131
  97. package/src/compiler/seo.ts +381 -356
  98. package/src/compiler/vite.ts +155 -145
  99. package/src/io/FastSet.ts +99 -96
  100. package/test/configure.test.ts +94 -90
  101. package/test/doctor.test.ts +140 -0
  102. package/test/dom/Image.test.tsx +73 -46
  103. package/test/dom/Script.test.tsx +48 -45
  104. package/test/dom/action.test.tsx +146 -129
  105. package/test/dom/error-overlay.test.tsx +1 -1
  106. package/test/dom/loader.test.tsx +2 -2
  107. package/test/dom/revalidate.test.tsx +1 -1
  108. package/test/dom/route-head.test.tsx +1 -2
  109. package/test/dom/router-loading.test.tsx +1 -1
  110. package/test/dom/slot.test.tsx +131 -109
  111. package/test/dom/view-transitions.test.tsx +53 -51
  112. package/test/features.test.ts +149 -142
  113. package/test/fonts.test.ts +28 -26
  114. package/test/head.test.ts +45 -35
  115. package/test/metadata.test.ts +42 -41
  116. package/test/pages.test.ts +105 -0
  117. package/test/prerender.test.ts +54 -46
  118. package/test/search.test.ts +114 -0
  119. package/test/seo.test.ts +30 -8
  120. package/test/update.test.ts +44 -0
@@ -1,96 +1,137 @@
1
- // The home page sets its own title with an absolute template (`%s`), so the tab reads exactly this
2
- // rather than being suffixed by the layout's "%s | ToilJS".
3
- export const metadata: Toil.Metadata = {
4
- title: 'ToilJS, the modern React framework',
5
- titleTemplate: '%s',
6
- description: 'File-based routing, instant HMR, build-time SEO, and a WebAssembly backend. Zero config.',
7
- openGraph: { title: 'ToilJS', type: 'website' },
8
- };
9
-
10
- const GitHubIcon = () => (
11
- <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
12
- <path d="M12 2C6.477 2 2 6.477 2 12c0 4.418 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.868-.013-1.703-2.782.604-3.369-1.341-3.369-1.341-.454-1.154-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0 1 12 6.836a9.59 9.59 0 0 1 2.504.337c1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.202 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.579.688.481C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z" />
13
- </svg>
14
- );
15
-
16
- const icons = {
17
- hmr: (
18
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
19
- <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
20
- </svg>
21
- ),
22
- routing: (
23
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
24
- <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
25
- </svg>
26
- ),
27
- typescript: (
28
- <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
29
- <rect x="2" y="2" width="20" height="20" rx="3" fill="currentColor" />
30
- <path d="M13.5 12H15.5V18H17V12H19V10.5H13.5V12Z" fill="var(--bg)" />
31
- <path d="M11 10.5C9.07 10.5 7.5 12.07 7.5 14C7.5 15.45 8.38 16.69 9.65 17.23L7.5 18H11C12.93 18 14.5 16.43 14.5 14.5C14.5 13.26 13.86 12.17 12.9 11.55C12.42 11.22 11.73 10.5 11 10.5ZM11 12C12.1 12 13 12.9 13 14C13 15.1 12.1 16 11 16H9.72C9.28 15.57 9 14.81 9 14C9 12.9 9.9 12 11 12Z" fill="var(--bg)" />
32
- </svg>
33
- ),
34
- builds: (
35
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
36
- <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
37
- <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
38
- <line x1="12" y1="22.08" x2="12" y2="12" />
39
- </svg>
40
- ),
41
- };
42
-
43
- const features = [
44
- { icon: icons.hmr, label: 'Instant HMR' },
45
- { icon: icons.routing, label: 'File Routing' },
46
- { icon: icons.typescript, label: 'TypeScript' },
47
- { icon: icons.builds, label: 'Optimized Builds' },
48
- ];
49
-
50
- export default function Home() {
51
- return (
52
- <section className="hero">
53
- <div className="hero-logo">
54
- <img src="/images/logo.svg" className="hero-logo-glow" alt="" aria-hidden="true" width={96} height={96} />
55
- <Toil.Image
56
- src="/images/logo.svg"
57
- className="hero-logo-img"
58
- alt="ToilJS"
59
- width={96}
60
- height={96}
61
- priority
62
- />
63
- </div>
64
-
65
- <h1 className="hero-title">ToilJS</h1>
66
-
67
- <p className="hero-tagline">
68
- Next-gen React.<br />
69
- <span>Zero config.</span>
70
- </p>
71
-
72
- <p className="hero-desc">
73
- File-based routing, blazing-fast HMR, and full TypeScript.
74
- <br />All powered by Vite.
75
- </p>
76
-
77
- <ul className="features">
78
- {features.map(f => (
79
- <li key={f.label} className="feature-badge">
80
- {f.icon}{f.label}
81
- </li>
82
- ))}
83
- </ul>
84
-
85
- <div className="hero-cta">
86
- <Toil.Link href="/get-started" className="btn btn-primary">
87
- Get Started
88
- </Toil.Link>
89
- <a className="btn btn-secondary" href="https://github.com/btc-vision/toiljs" target="_blank" rel="noopener noreferrer">
90
- <GitHubIcon />
91
- GitHub
92
- </a>
93
- </div>
94
- </section>
95
- );
96
- }
1
+ // The home page sets its own title with an absolute template (`%s`), so the tab reads exactly this
2
+ // rather than being suffixed by the layout's "%s | ToilJS".
3
+ export const metadata: Toil.Metadata = {
4
+ title: 'ToilJS, the modern React framework',
5
+ titleTemplate: '%s',
6
+ description: 'File-based routing, instant HMR, build-time SEO, and a WebAssembly backend. Zero config.',
7
+ openGraph: { title: 'ToilJS', type: 'website' }
8
+ };
9
+
10
+ const GitHubIcon = () => (
11
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
12
+ <path d="M12 2C6.477 2 2 6.477 2 12c0 4.418 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.868-.013-1.703-2.782.604-3.369-1.341-3.369-1.341-.454-1.154-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0 1 12 6.836a9.59 9.59 0 0 1 2.504.337c1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.202 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.579.688.481C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z" />
13
+ </svg>
14
+ );
15
+
16
+ const icons = {
17
+ hmr: (
18
+ <svg
19
+ width="16"
20
+ height="16"
21
+ viewBox="0 0 24 24"
22
+ fill="none"
23
+ stroke="currentColor"
24
+ strokeWidth="2"
25
+ strokeLinecap="round"
26
+ strokeLinejoin="round">
27
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
28
+ </svg>
29
+ ),
30
+ routing: (
31
+ <svg
32
+ width="16"
33
+ height="16"
34
+ viewBox="0 0 24 24"
35
+ fill="none"
36
+ stroke="currentColor"
37
+ strokeWidth="2"
38
+ strokeLinecap="round"
39
+ strokeLinejoin="round">
40
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
41
+ </svg>
42
+ ),
43
+ typescript: (
44
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
45
+ <rect x="2" y="2" width="20" height="20" rx="3" fill="currentColor" />
46
+ <path d="M13.5 12H15.5V18H17V12H19V10.5H13.5V12Z" fill="var(--bg)" />
47
+ <path
48
+ d="M11 10.5C9.07 10.5 7.5 12.07 7.5 14C7.5 15.45 8.38 16.69 9.65 17.23L7.5 18H11C12.93 18 14.5 16.43 14.5 14.5C14.5 13.26 13.86 12.17 12.9 11.55C12.42 11.22 11.73 10.5 11 10.5ZM11 12C12.1 12 13 12.9 13 14C13 15.1 12.1 16 11 16H9.72C9.28 15.57 9 14.81 9 14C9 12.9 9.9 12 11 12Z"
49
+ fill="var(--bg)"
50
+ />
51
+ </svg>
52
+ ),
53
+ builds: (
54
+ <svg
55
+ width="16"
56
+ height="16"
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ strokeWidth="2"
61
+ strokeLinecap="round"
62
+ strokeLinejoin="round">
63
+ <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
64
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
65
+ <line x1="12" y1="22.08" x2="12" y2="12" />
66
+ </svg>
67
+ )
68
+ };
69
+
70
+ const features = [
71
+ { icon: icons.hmr, label: 'Instant HMR' },
72
+ { icon: icons.routing, label: 'File Routing' },
73
+ { icon: icons.typescript, label: 'TypeScript' },
74
+ { icon: icons.builds, label: 'Optimized Builds' }
75
+ ];
76
+
77
+ export default function Home() {
78
+ return (
79
+ <section className="hero">
80
+ <div className="hero-logo">
81
+ <img
82
+ src="/images/logo.svg"
83
+ className="hero-logo-glow"
84
+ alt=""
85
+ aria-hidden="true"
86
+ width={96}
87
+ height={96}
88
+ />
89
+ <Toil.Image
90
+ src="/images/logo.svg"
91
+ className="hero-logo-img"
92
+ alt="ToilJS"
93
+ width={96}
94
+ height={96}
95
+ priority
96
+ />
97
+ </div>
98
+
99
+ <h1 className="hero-title">ToilJS</h1>
100
+
101
+ <p className="hero-tagline">
102
+ Next-gen React.
103
+ <br />
104
+ <span>Zero config.</span>
105
+ </p>
106
+
107
+ <p className="hero-desc">
108
+ File-based routing, blazing-fast HMR, and full TypeScript.
109
+ <br />
110
+ All powered by Vite.
111
+ </p>
112
+
113
+ <ul className="features">
114
+ {features.map((f) => (
115
+ <li key={f.label} className="feature-badge">
116
+ {f.icon}
117
+ {f.label}
118
+ </li>
119
+ ))}
120
+ </ul>
121
+
122
+ <div className="hero-cta">
123
+ <Toil.Link href="/get-started" className="btn btn-primary">
124
+ Get Started
125
+ </Toil.Link>
126
+ <a
127
+ className="btn btn-secondary"
128
+ href="https://github.com/btc-vision/toiljs"
129
+ target="_blank"
130
+ rel="noopener noreferrer">
131
+ <GitHubIcon />
132
+ GitHub
133
+ </a>
134
+ </div>
135
+ </section>
136
+ );
137
+ }
@@ -1,52 +1,59 @@
1
- async function wait(ms: number): Promise<void> {
2
- return new Promise((resolve) => setTimeout(resolve, ms));
3
- }
4
-
5
- export const loader = async ({ searchParams }: Toil.LoaderArgs) => {
6
- await wait(2000);
7
- return { loadedAt: new Date().toISOString(), q: searchParams.get('q') };
8
- };
9
-
10
- // Cache this route's data for 10s: revisiting within 10s is instant (no 2s wait); after that it
11
- // refetches on navigation. Use `false` to cache forever, or omit for the default (refetch every nav).
12
- export const revalidate: Toil.Revalidate = 10;
13
-
14
- // Dynamic metadata derived from the loader's data (vs the static `metadata` export on /about).
15
- export const generateMetadata: Toil.GenerateMetadata<Awaited<ReturnType<typeof loader>>> = ({
16
- data,
17
- }) => ({ title: `Loader demo, loaded ${data.loadedAt}` });
18
-
19
- export default function LoaderDemo() {
20
- // Pass the loader to infer the data type from its return, no generics, no restating the shape.
21
- const data = Toil.useLoaderData(loader);
22
- const router = Toil.useRouter();
23
- return (
24
- <main>
25
- <h1>Loader demo</h1>
26
- <p>
27
- Data loaded before render (no <code>useEffect</code>): <code>{data.loadedAt}</code>
28
- {data.q !== null ? `, q=${data.q}` : ''}
29
- </p>
30
- <p>
31
- <button type="button" onClick={() => { router.revalidate(); }}>
32
- Revalidate (refetch)
33
- </button>
34
- </p>
35
- {/* The write half: an action runs on submit, then revalidates this route's loader so
36
- `loadedAt` above updates, read, write, revalidate, no manual refetch. */}
37
- <Toil.Form
38
- action={async (form) => { await wait(500); console.log('saved', form.get('note')); }}
39
- revalidate>
40
- {({ pending }) => (
41
- <>
42
- <input name="note" placeholder="Leave a note" disabled={pending} />
43
- <button type="submit" disabled={pending}>
44
- {pending ? 'Saving' : 'Save & revalidate'}
45
- </button>
46
- </>
47
- )}
48
- </Toil.Form>
49
- <Toil.Link href="/">Back home</Toil.Link>
50
- </main>
51
- );
52
- }
1
+ async function wait(ms: number): Promise<void> {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ }
4
+
5
+ export const loader = async ({ searchParams }: Toil.LoaderArgs) => {
6
+ await wait(2000);
7
+ return { loadedAt: new Date().toISOString(), q: searchParams.get('q') };
8
+ };
9
+
10
+ // Cache this route's data for 10s: revisiting within 10s is instant (no 2s wait); after that it
11
+ // refetches on navigation. Use `false` to cache forever, or omit for the default (refetch every nav).
12
+ export const revalidate: Toil.Revalidate = 10;
13
+
14
+ // Dynamic metadata derived from the loader's data (vs the static `metadata` export on /about).
15
+ export const generateMetadata: Toil.GenerateMetadata<Awaited<ReturnType<typeof loader>>> = ({ data }) => ({
16
+ title: `Loader demo, loaded ${data.loadedAt}`
17
+ });
18
+
19
+ export default function LoaderDemo() {
20
+ // Pass the loader to infer the data type from its return, no generics, no restating the shape.
21
+ const data = Toil.useLoaderData(loader);
22
+ const router = Toil.useRouter();
23
+ return (
24
+ <main>
25
+ <h1>Loader demo</h1>
26
+ <p>
27
+ Data loaded before render (no <code>useEffect</code>): <code>{data.loadedAt}</code>
28
+ {data.q !== null ? `, q=${data.q}` : ''}
29
+ </p>
30
+ <p>
31
+ <button
32
+ type="button"
33
+ onClick={() => {
34
+ router.revalidate();
35
+ }}>
36
+ Revalidate (refetch)
37
+ </button>
38
+ </p>
39
+ {/* The write half: an action runs on submit, then revalidates this route's loader so
40
+ `loadedAt` above updates, read, write, revalidate, no manual refetch. */}
41
+ <Toil.Form
42
+ action={async (form) => {
43
+ await wait(500);
44
+ console.log('saved', form.get('note'));
45
+ }}
46
+ revalidate>
47
+ {({ pending }) => (
48
+ <>
49
+ <input name="note" placeholder="Leave a note" disabled={pending} />
50
+ <button type="submit" disabled={pending}>
51
+ {pending ? 'Saving' : 'Save & revalidate'}
52
+ </button>
53
+ </>
54
+ )}
55
+ </Toil.Form>
56
+ <Toil.Link href="/">Back home</Toil.Link>
57
+ </main>
58
+ );
59
+ }
@@ -0,0 +1,61 @@
1
+ import { useState } from 'react';
2
+
3
+ export const metadata: Toil.Metadata = {
4
+ title: 'Search',
5
+ description: 'Search every page by its metadata and jump straight to it.',
6
+ keywords: ['search', 'find', 'pages', 'metadata']
7
+ };
8
+
9
+ // A tiny site-wide search box. `usePageSearch` queries the compiler-built index of every page's
10
+ // metadata (title/description/keywords/OpenGraph + static `searchHints` on dynamic routes), returns
11
+ // ranked matches with their route `path`, and `goTo` navigates to whichever one you pick.
12
+ export default function Search() {
13
+ const [query, setQuery] = useState('');
14
+ const { results, pages, goTo } = Toil.usePageSearch(query, { includeDynamic: true });
15
+
16
+ return (
17
+ <main>
18
+ <h1>Search</h1>
19
+ <p>
20
+ Type to search across the metadata of all {pages.length} pages, title, description, keywords, and
21
+ OpenGraph. Indexed at build by <code>client/routes/*</code>.
22
+ </p>
23
+
24
+ <input
25
+ type="search"
26
+ value={query}
27
+ onChange={(e) => {
28
+ setQuery(e.target.value);
29
+ }}
30
+ placeholder="Search pages… (try “blog”, “features”, “started”)"
31
+ aria-label="Search pages"
32
+ autoFocus
33
+ style={{ width: '100%', padding: '0.6rem 0.8rem', fontSize: '1rem' }}
34
+ />
35
+
36
+ {query.trim() !== '' && (
37
+ <ul style={{ listStyle: 'none', padding: 0, marginTop: '1rem' }}>
38
+ {results.length === 0 && <li>No pages match “{query}”.</li>}
39
+ {results.map((r) => (
40
+ <li key={r.page.path} style={{ marginBottom: '0.75rem' }}>
41
+ <button
42
+ type="button"
43
+ onClick={() => {
44
+ goTo(r);
45
+ }}
46
+ disabled={r.page.dynamic}
47
+ title={r.page.dynamic ? 'Dynamic route, needs params to open' : undefined}
48
+ style={{ textAlign: 'left', cursor: r.page.dynamic ? 'default' : 'pointer' }}>
49
+ <strong>{r.page.metadata.title ?? r.page.path}</strong> <code>{r.page.path}</code>
50
+ {r.page.metadata.description !== undefined && <div>{r.page.metadata.description}</div>}
51
+ <small>
52
+ score {r.score.toFixed(1)} · matched {r.matches.join(', ')}
53
+ </small>
54
+ </button>
55
+ </li>
56
+ ))}
57
+ </ul>
58
+ )}
59
+ </main>
60
+ );
61
+ }
@@ -1,8 +1,7 @@
1
- export default function TestPage() {
2
- return (
3
- <div className="test-page">
4
- <img src="/images/test_image.webp" alt="Test" className="test-page-image" />
5
- </div>
6
- );
7
- }
8
-
1
+ export default function TestPage() {
2
+ return (
3
+ <div className="test-page">
4
+ <img src="/images/test_image.webp" alt="Test" className="test-page-image" />
5
+ </div>
6
+ );
7
+ }