@tanstack/create 0.61.6 → 0.62.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 (101) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/config-file.js +5 -2
  3. package/dist/custom-add-ons/starter.js +45 -28
  4. package/dist/file-helpers.js +1 -0
  5. package/dist/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
  6. package/dist/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  7. package/dist/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
  8. package/dist/frameworks/react/add-ons/store/package.json +2 -2
  9. package/dist/frameworks/react/index.js +2 -2
  10. package/dist/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
  11. package/dist/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
  12. package/dist/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
  13. package/dist/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
  14. package/dist/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
  15. package/dist/frameworks/react/project/base/content-collections.ts.ejs +37 -0
  16. package/dist/frameworks/react/project/base/package.json +8 -1
  17. package/dist/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
  18. package/dist/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
  19. package/dist/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
  20. package/dist/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
  21. package/dist/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
  22. package/dist/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
  23. package/dist/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
  24. package/dist/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
  25. package/dist/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
  26. package/dist/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
  27. package/dist/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
  28. package/dist/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
  29. package/dist/frameworks/react/project/base/src/main.tsx.ejs +0 -1
  30. package/dist/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
  31. package/dist/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
  32. package/dist/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
  33. package/dist/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
  34. package/dist/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
  35. package/dist/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
  36. package/dist/frameworks/react/project/base/src/styles.css.ejs +268 -6
  37. package/dist/frameworks/react/project/base/tsconfig.json.ejs +2 -0
  38. package/dist/frameworks/react/project/base/vite.config.ts.ejs +2 -0
  39. package/dist/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  40. package/dist/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
  41. package/dist/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
  42. package/dist/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
  43. package/dist/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
  44. package/dist/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
  45. package/dist/frameworks.js +3 -0
  46. package/dist/package-json.js +1 -1
  47. package/dist/registry.js +21 -4
  48. package/dist/types/registry.d.ts +38 -0
  49. package/package.json +1 -1
  50. package/src/config-file.ts +6 -2
  51. package/src/custom-add-ons/starter.ts +30 -10
  52. package/src/file-helpers.ts +1 -0
  53. package/src/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
  54. package/src/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  55. package/src/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
  56. package/src/frameworks/react/add-ons/store/package.json +2 -2
  57. package/src/frameworks/react/index.ts +2 -2
  58. package/src/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
  59. package/src/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
  60. package/src/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
  61. package/src/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
  62. package/src/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
  63. package/src/frameworks/react/project/base/content-collections.ts.ejs +37 -0
  64. package/src/frameworks/react/project/base/package.json +8 -1
  65. package/src/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
  66. package/src/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
  67. package/src/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
  68. package/src/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
  69. package/src/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
  70. package/src/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
  71. package/src/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
  72. package/src/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
  73. package/src/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
  74. package/src/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
  75. package/src/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
  76. package/src/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
  77. package/src/frameworks/react/project/base/src/main.tsx.ejs +0 -1
  78. package/src/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
  79. package/src/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
  80. package/src/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
  81. package/src/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
  82. package/src/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
  83. package/src/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
  84. package/src/frameworks/react/project/base/src/styles.css.ejs +268 -6
  85. package/src/frameworks/react/project/base/tsconfig.json.ejs +2 -0
  86. package/src/frameworks/react/project/base/vite.config.ts.ejs +2 -0
  87. package/src/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  88. package/src/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
  89. package/src/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
  90. package/src/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
  91. package/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
  92. package/src/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
  93. package/src/frameworks.ts +4 -0
  94. package/src/package-json.ts +1 -1
  95. package/src/registry.ts +28 -4
  96. package/tests/add-ons.test.ts +4 -4
  97. package/tests/config-file.test.ts +3 -3
  98. package/tests/custom-add-ons/starter.test.ts +34 -2
  99. package/tests/frameworks.test.ts +24 -0
  100. package/tests/options.test.ts +4 -4
  101. package/tests/utils.test.ts +2 -2
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: 'Midnight Compass Build'
3
+ description: 'Where to customize theme and typography.'
4
+ pubDate: 'Jul 22 2024'
5
+ heroImage: '/images/lagoon-2.svg'
6
+ ---
7
+
8
+ Update CSS variables in `src/styles.css` to fit your brand.
9
+
10
+ Then adjust header and footer links to match your product.
11
+
12
+ ## Theme alignment checklist
13
+
14
+ Before adding one-off colors, audit these variables:
15
+
16
+ - `--sea-ink` and `--sea-ink-soft` for readable body copy
17
+ - `--surface` and `--surface-strong` for cards and shells
18
+ - `--lagoon` / `--lagoon-deep` for links and active UI affordances
19
+
20
+ If those are correct, most components will look coherent with zero extra work.
21
+
22
+ ### Accessibility reminder
23
+
24
+ Check contrast on at least three surfaces:
25
+
26
+ 1. page background
27
+ 2. primary card
28
+ 3. muted card
29
+
30
+ You can be highly branded and still hit comfortable readability.
31
+
32
+ ## Typography defaults that travel well
33
+
34
+ Use a high-contrast display face for headlines and a workhorse sans for body
35
+ copy. Then lock in a spacing scale that keeps article rhythm consistent:
36
+
37
+ - headings: `mt-10 mb-3`
38
+ - paragraphs: `mb-5`
39
+ - lists: `mb-6`
40
+
41
+ With those defaults set, long-form pages stay readable across both themes.
42
+
43
+ ### Practical review loop
44
+
45
+ When you tweak tokens, review these pages in order:
46
+
47
+ 1. Blog detail page (most typography states)
48
+ 2. Blog index page (cards + metadata)
49
+ 3. Home page (hero and CTA emphasis)
@@ -0,0 +1,37 @@
1
+ import { defineCollection, defineConfig } from '@content-collections/core'
2
+ import { compileMarkdown } from '@content-collections/markdown'
3
+ import { compileMDX } from '@content-collections/mdx'
4
+ import remarkGfm from 'remark-gfm'
5
+ import { z } from 'zod'
6
+
7
+ const blog = defineCollection({
8
+ name: 'blog',
9
+ directory: 'content/blog',
10
+ include: '**/*.{md,mdx}',
11
+ schema: z.object({
12
+ title: z.string(),
13
+ description: z.string(),
14
+ pubDate: z.string(),
15
+ content: z.string(),
16
+ heroImage: z.string().optional(),
17
+ }),
18
+ transform: async (document, context) => {
19
+ const isMdx = document._meta.filePath.endsWith('.mdx')
20
+
21
+ return {
22
+ ...document,
23
+ slug: document._meta.path,
24
+ pubDate: new Date(document.pubDate).toISOString(),
25
+ html: isMdx ? null : await compileMarkdown(context, document),
26
+ mdx: isMdx
27
+ ? await compileMDX(context, document, {
28
+ remarkPlugins: [remarkGfm],
29
+ })
30
+ : null,
31
+ }
32
+ },
33
+ })
34
+
35
+ export default defineConfig({
36
+ collections: [blog],
37
+ })
@@ -22,6 +22,11 @@
22
22
  "react-dom": "^19.2.0"
23
23
  },
24
24
  "devDependencies": {
25
+ "@content-collections/core": "^0.13.1",
26
+ "@content-collections/markdown": "^0.1.4",
27
+ "@content-collections/mdx": "^0.1.2",
28
+ "@content-collections/vite": "^0.2.8",
29
+ "@tailwindcss/typography": "^0.5.16",
25
30
  "@tanstack/devtools-vite": "^0.3.11",
26
31
  "@testing-library/dom": "^10.4.0",
27
32
  "@testing-library/react": "^16.2.0",
@@ -29,9 +34,11 @@
29
34
  "@types/react-dom": "^19.2.0",
30
35
  "@vitejs/plugin-react": "^5.0.4",
31
36
  "jsdom": "^27.0.0",
37
+ "remark-gfm": "^4.0.1",
32
38
  "typescript": "^5.7.2",
33
39
  "vite": "^7.1.7",
34
40
  "vite-tsconfig-paths": "^5.1.4",
35
- "vitest": "^3.0.5"
41
+ "vitest": "^3.0.5",
42
+ "zod": "^4.3.5"
36
43
  }
37
44
  }
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-label="Lagoon gradient 1">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
4
+ <stop offset="0%" stop-color="#9be2dc"/>
5
+ <stop offset="55%" stop-color="#65c2c4"/>
6
+ <stop offset="100%" stop-color="#356f7b"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect width="1200" height="630" fill="url(#bg)"/>
10
+ <circle cx="980" cy="90" r="120" fill="#fff" fill-opacity="0.32"/>
11
+ <path d="M0 500c160-70 280-70 420 0s260 70 420 0 240-70 360 0v130H0z" fill="#d8f4ec" fill-opacity="0.82"/>
12
+ <path d="M0 550c160-70 280-70 420 0s260 70 420 0 240-70 360 0v80H0z" fill="#2f6a4a" fill-opacity="0.18"/>
13
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-label="Lagoon gradient 2">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
4
+ <stop offset="0%" stop-color="#b1ebe4"/>
5
+ <stop offset="50%" stop-color="#73c7b3"/>
6
+ <stop offset="100%" stop-color="#2f6a4a"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect width="1200" height="630" fill="url(#bg)"/>
10
+ <path d="M0 485c180-96 360-96 540 0s360 96 660 0v145H0z" fill="#f1fbf8" fill-opacity="0.72"/>
11
+ <circle cx="180" cy="140" r="90" fill="#fff" fill-opacity="0.26"/>
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-label="Lagoon gradient 3">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
4
+ <stop offset="0%" stop-color="#8fdcd0"/>
5
+ <stop offset="60%" stop-color="#4ea7ad"/>
6
+ <stop offset="100%" stop-color="#1f4f5c"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect width="1200" height="630" fill="url(#bg)"/>
10
+ <path d="M0 520c160-70 320-70 480 0s320 70 720 0v110H0z" fill="#dff6ee" fill-opacity="0.8"/>
11
+ <path d="M0 560c160-70 320-70 480 0s320 70 720 0v70H0z" fill="#173a40" fill-opacity="0.16"/>
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-label="Lagoon gradient 4">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" x2="1" y1="1" y2="0">
4
+ <stop offset="0%" stop-color="#81d8c9"/>
5
+ <stop offset="52%" stop-color="#53a9c8"/>
6
+ <stop offset="100%" stop-color="#2f6a7b"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect width="1200" height="630" fill="url(#bg)"/>
10
+ <circle cx="1040" cy="140" r="96" fill="#fff" fill-opacity="0.24"/>
11
+ <path d="M0 500c180-85 350-85 520 0s350 85 680 0v130H0z" fill="#effaf6" fill-opacity="0.74"/>
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-label="Lagoon gradient 5">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
4
+ <stop offset="0%" stop-color="#9cded4"/>
5
+ <stop offset="48%" stop-color="#6fb9cf"/>
6
+ <stop offset="100%" stop-color="#24566b"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect width="1200" height="630" fill="url(#bg)"/>
10
+ <path d="M0 505c160-65 320-65 480 0s320 65 720 0v125H0z" fill="#edf9f4" fill-opacity="0.76"/>
11
+ <circle cx="260" cy="120" r="102" fill="#fff" fill-opacity="0.2"/>
12
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-label="Lagoon about illustration">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
4
+ <stop offset="0%" stop-color="#bceee3"/>
5
+ <stop offset="54%" stop-color="#8cc5be"/>
6
+ <stop offset="100%" stop-color="#3b7b78"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect width="1200" height="630" fill="url(#bg)"/>
10
+ <rect x="160" y="130" width="880" height="370" rx="34" fill="#fff" fill-opacity="0.28"/>
11
+ <path d="M220 424h760" stroke="#fff" stroke-opacity="0.6" stroke-width="10" stroke-linecap="round"/>
12
+ <path d="M220 468h600" stroke="#fff" stroke-opacity="0.46" stroke-width="10" stroke-linecap="round"/>
13
+ <circle cx="980" cy="190" r="72" fill="#fff" fill-opacity="0.22"/>
14
+ </svg>
@@ -0,0 +1,42 @@
1
+ export default function Footer() {
2
+ const year = new Date().getFullYear()
3
+
4
+ return (
5
+ <footer className="mt-20 border-t border-[var(--line)] px-4 pb-14 pt-10 text-[var(--sea-ink-soft)]">
6
+ <div className="page-wrap flex flex-col items-center justify-between gap-4 text-center sm:flex-row sm:text-left">
7
+ <p className="m-0 text-sm">&copy; {year} Your name here. All rights reserved.</p>
8
+ <p className="island-kicker m-0">Built with TanStack Start</p>
9
+ </div>
10
+ <div className="mt-4 flex justify-center gap-4">
11
+ <a
12
+ href="https://x.com/tan_stack"
13
+ target="_blank"
14
+ rel="noreferrer"
15
+ className="rounded-xl p-2 text-[var(--sea-ink-soft)] transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)]"
16
+ >
17
+ <span className="sr-only">Follow TanStack on X</span>
18
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
19
+ <path
20
+ fill="currentColor"
21
+ d="M12.6 1h2.2L10 6.48 15.64 15h-4.41L7.78 9.82 3.23 15H1l5.14-5.84L.72 1h4.52l3.12 4.73L12.6 1zm-.77 12.67h1.22L4.57 2.26H3.26l8.57 11.41z"
22
+ />
23
+ </svg>
24
+ </a>
25
+ <a
26
+ href="https://github.com/TanStack"
27
+ target="_blank"
28
+ rel="noreferrer"
29
+ className="rounded-xl p-2 text-[var(--sea-ink-soft)] transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)]"
30
+ >
31
+ <span className="sr-only">Go to TanStack GitHub</span>
32
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
33
+ <path
34
+ fill="currentColor"
35
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
36
+ />
37
+ </svg>
38
+ </a>
39
+ </div>
40
+ </footer>
41
+ )
42
+ }
@@ -1,156 +1,110 @@
1
- <% if (addOns.length === 0 && integrations.length === 0 && routes.length === 0) { ignoreFile() } %>import { Link } from '@tanstack/react-router'
2
- <% for(const integration of integrations.filter(i => i.type === 'header-user')) { %>
3
- import <%= integration.jsName %> from "<%= relativePath(integration.path) %>";
4
- <% } %><%
5
- const icons = new Set([
6
- "Menu",
7
- "X",
8
- "Home",
9
- ])
1
+ import { Link } from '@tanstack/react-router'
2
+ <% for (const integration of integrations.filter((i) => i.type === 'header-user')) { %>import <%= integration.jsName %> from '<%= relativePath(integration.path) %>'
3
+ <% } %>import ThemeToggle from './ThemeToggle'
10
4
 
11
- const hasRoutesWithChildren = addOns.some(a => a.routes?.some(r => r.children));
12
-
13
- if (hasRoutesWithChildren) {
14
- icons.add("ChevronDown");
15
- icons.add("ChevronRight");
16
- }
17
-
18
- for(const addOn of addOns) {
19
- for(const route of (addOn?.routes||[])?.filter(r => r.url && r.name)) {
20
- icons.add( route.icon || "Globe");
5
+ <%
6
+ const demoRoutes = [];
7
+ for (const addOn of addOns) {
8
+ for (const route of (addOn?.routes || []).filter((r) => r.url && r.name)) {
9
+ demoRoutes.push({ url: route.url, name: route.name });
10
+ for (const child of route.children || []) {
11
+ if (child?.url && child?.name) {
12
+ demoRoutes.push({ url: child.url, name: child.name });
13
+ }
14
+ }
21
15
  }
22
16
  }
17
+ const userHeaders = integrations.filter((i) => i.type === 'header-user');
23
18
  %>
24
- import { useState } from 'react';
25
- import {
26
- <%= Array.from(icons).sort().join(", ") %>
27
- } from "lucide-react";
28
19
 
29
20
  export default function Header() {
30
- <%
31
- const menusWithChildren = addOns.filter(a => a.routes?.some(r => r.children));
32
- const userHeaders = integrations.filter(i => i.type === 'header-user');
33
- %>
34
- const [isOpen, setIsOpen] = useState(false);
35
- <% if (menusWithChildren.length > 0) { %>const [groupedExpanded, setGroupedExpanded] = useState<Record<string, boolean>>({});
36
- <% } %>
37
-
38
21
  return (
39
- <>
40
- <header className="p-4 flex items-center bg-gray-800 text-white shadow-lg">
41
- <button
42
- onClick={() => setIsOpen(true)}
43
- className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
44
- aria-label="Open menu"
45
- >
46
- <Menu size={24} />
47
- </button>
48
- <h1 className="ml-4 text-xl font-semibold">
49
- <Link to="/">
50
- <img
51
- src="/tanstack-word-logo-white.svg"
52
- alt="TanStack Logo"
53
- className="h-10"
54
- />
22
+ <header className="sticky top-0 z-50 border-b border-[var(--line)] bg-[var(--header-bg)] px-4 backdrop-blur-lg">
23
+ <nav className="page-wrap flex flex-wrap items-center gap-x-3 gap-y-2 py-3 sm:py-4">
24
+ <h2 className="m-0 flex-shrink-0 text-base font-semibold tracking-tight">
25
+ <Link
26
+ to="/"
27
+ className="inline-flex items-center gap-2 rounded-full border border-[var(--chip-line)] bg-[var(--chip-bg)] px-3 py-1.5 text-sm text-[var(--sea-ink)] no-underline shadow-[0_8px_24px_rgba(30,90,72,0.08)] sm:px-4 sm:py-2"
28
+ >
29
+ <span className="h-2 w-2 rounded-full bg-[linear-gradient(90deg,#56c6be,#7ed3bf)]" />
30
+ TanStack Start
55
31
  </Link>
56
- </h1>
57
- </header>
32
+ </h2>
58
33
 
59
- <aside
60
- className={`fixed top-0 left-0 h-full w-80 bg-gray-900 text-white shadow-2xl z-50 transform transition-transform duration-300 ease-in-out flex flex-col ${
61
- isOpen ? "translate-x-0" : "-translate-x-full"
62
- }`}
63
- >
64
- <div className="flex items-center justify-between p-4 border-b border-gray-700">
65
- <h2 className="text-xl font-bold">Navigation</h2>
66
- <button
67
- onClick={() => setIsOpen(false)}
68
- className="p-2 hover:bg-gray-800 rounded-lg transition-colors"
69
- aria-label="Close menu"
34
+ <div className="ml-auto flex items-center gap-1.5 sm:ml-0 sm:gap-2">
35
+ <a
36
+ href="https://x.com/tan_stack"
37
+ target="_blank"
38
+ rel="noreferrer"
39
+ className="hidden rounded-xl p-2 text-[var(--sea-ink-soft)] transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)] sm:block"
40
+ >
41
+ <span className="sr-only">Follow TanStack on X</span>
42
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="24" height="24">
43
+ <path
44
+ fill="currentColor"
45
+ d="M12.6 1h2.2L10 6.48 15.64 15h-4.41L7.78 9.82 3.23 15H1l5.14-5.84L.72 1h4.52l3.12 4.73L12.6 1zm-.77 12.67h1.22L4.57 2.26H3.26l8.57 11.41z"
46
+ />
47
+ </svg>
48
+ </a>
49
+ <a
50
+ href="https://github.com/TanStack"
51
+ target="_blank"
52
+ rel="noreferrer"
53
+ className="hidden rounded-xl p-2 text-[var(--sea-ink-soft)] transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)] sm:block"
70
54
  >
71
- <X size={24} />
72
- </button>
55
+ <span className="sr-only">Go to TanStack GitHub</span>
56
+ <svg viewBox="0 0 16 16" aria-hidden="true" width="24" height="24">
57
+ <path
58
+ fill="currentColor"
59
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
60
+ />
61
+ </svg>
62
+ </a>
63
+ <% for (const integration of userHeaders) { %><<%= integration.jsName %> />
64
+ <% } %>
65
+ <ThemeToggle />
73
66
  </div>
74
67
 
75
- <nav className="flex-1 p-4 overflow-y-auto">
68
+ <div className="order-3 flex w-full flex-wrap items-center gap-x-4 gap-y-1 pb-1 text-sm font-semibold sm:order-2 sm:w-auto sm:flex-nowrap sm:pb-0">
69
+ <Link to="/" className="nav-link" activeProps={{ className: 'nav-link is-active' }}>
70
+ Home
71
+ </Link>
76
72
  <Link
77
- to="/"
78
- onClick={() => setIsOpen(false)}
79
- className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
80
- activeProps={{
81
- className:
82
- "flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2",
83
- }}
73
+ to="/blog"
74
+ className="nav-link"
75
+ activeProps={{ className: 'nav-link is-active' }}
84
76
  >
85
- <Home size={20} />
86
- <span className="font-medium">Home</span>
77
+ Blog
87
78
  </Link>
88
-
89
- {/* Demo Links Start */}
90
- <% for(const addOn of addOns) {
91
- for(const route of (addOn?.routes||[])?.filter(r => r.url && r.name)) {
92
- if (route.children) { %>
93
- <div className="flex flex-row justify-between">
94
- <Link to="<%= route.url %>"
95
- onClick={() => setIsOpen(false)}
96
- className="flex-1 flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
97
- activeProps={{
98
- className:
99
- "flex-1 flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2",
100
- }}
101
- >
102
- <<%= route.icon || "Globe" %> size={20} />
103
- <span className="font-medium"><%= route.name %></span>
104
- </Link>
105
- <button
106
- className="p-2 hover:bg-gray-800 rounded-lg transition-colors"
107
- onClick={() => setGroupedExpanded(prev => ({ ...prev, <%= route.jsName %>: !prev.<%= route.jsName %> }))}>
108
- {groupedExpanded.<%= route.jsName %> ? <ChevronDown size={20} /> : <ChevronRight size={20} />}
109
- </button>
110
- </div>
111
- {groupedExpanded.<%= route.jsName %> && (
112
- <div className="flex flex-col ml-4">
113
- <% for(const child of route.children) { %>
114
- <Link
115
- to="<%= child.url %>"
116
- onClick={() => setIsOpen(false)}
117
- className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
118
- activeProps={{
119
- className:
120
- "flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2",
121
- }}
122
- >
123
- <<%= child.icon || "Globe" %> size={20} />
124
- <span className="font-medium"><%= child.name %></span>
125
- </Link>
126
- <% } %>
127
- </div>
128
- )}
129
- <% } else { %>
130
- <Link
131
- to="<%= route.url %>"
132
- onClick={() => setIsOpen(false)}
133
- className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
134
- activeProps={{
135
- className:
136
- "flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2",
137
- }}
79
+ <Link
80
+ to="/about"
81
+ className="nav-link"
82
+ activeProps={{ className: 'nav-link is-active' }}
83
+ >
84
+ About
85
+ </Link>
86
+ <a
87
+ href="https://tanstack.com/start/latest/docs/framework/react/overview"
88
+ className="nav-link"
89
+ target="_blank"
90
+ rel="noreferrer"
91
+ >
92
+ Docs
93
+ </a>
94
+ <% if (demoRoutes.length > 0) { %><details className="relative w-full sm:w-auto">
95
+ <summary className="nav-link list-none cursor-pointer">Demos</summary>
96
+ <div className="mt-2 min-w-56 rounded-xl border border-[var(--line)] bg-[var(--header-bg)] p-2 shadow-lg sm:absolute sm:right-0">
97
+ <% for (const route of demoRoutes) { %><a
98
+ href="<%= route.url %>"
99
+ className="block rounded-lg px-3 py-2 text-sm text-[var(--sea-ink-soft)] no-underline transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)]"
138
100
  >
139
- <<%= route.icon || "Globe" %> size={20} />
140
- <span className="font-medium"><%= route.name %></span>
141
- </Link>
142
- <% } } } %>
143
- {/* Demo Links End */}
144
- </nav>
145
-
146
- <% if (userHeaders.length > 0) { %>
147
- <div className="p-4 border-t border-gray-700 bg-gray-800 flex flex-col gap-2">
148
- <% for(const integration of userHeaders) { %>
149
- <<%= integration.jsName %> />
150
- <% } %>
101
+ <%= route.name %>
102
+ </a>
103
+ <% } %>
104
+ </div>
105
+ </details><% } %>
151
106
  </div>
152
- <% } %>
153
- </aside>
154
- </>
155
- );
107
+ </nav>
108
+ </header>
109
+ )
156
110
  }
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export function MdxCallout({
4
+ title,
5
+ children,
6
+ }: {
7
+ title: string
8
+ children: ReactNode
9
+ }) {
10
+ return (
11
+ <aside className="not-prose my-6 rounded-xl border border-[var(--line)] bg-[var(--chip-bg)] p-4">
12
+ <p className="island-kicker mb-2">{title}</p>
13
+ <div className="text-sm leading-7 text-[var(--sea-ink-soft)]">{children}</div>
14
+ </aside>
15
+ )
16
+ }
@@ -0,0 +1,23 @@
1
+ export function MdxMetrics({
2
+ items,
3
+ }: {
4
+ items: Array<{ label: string; value: string }>
5
+ }) {
6
+ return (
7
+ <div className="not-prose my-6 grid gap-3 sm:grid-cols-3">
8
+ {items.map((item) => (
9
+ <div
10
+ key={item.label}
11
+ className="rounded-xl border border-[var(--line)] bg-[var(--chip-bg)] px-4 py-3"
12
+ >
13
+ <p className="m-0 text-xs uppercase tracking-[0.12em] text-[var(--sea-ink-soft)]">
14
+ {item.label}
15
+ </p>
16
+ <p className="m-0 mt-1 text-lg font-semibold text-[var(--sea-ink)]">
17
+ {item.value}
18
+ </p>
19
+ </div>
20
+ ))}
21
+ </div>
22
+ )
23
+ }
@@ -0,0 +1,81 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ type ThemeMode = 'light' | 'dark' | 'auto'
4
+
5
+ function getInitialMode(): ThemeMode {
6
+ if (typeof window === 'undefined') {
7
+ return 'auto'
8
+ }
9
+
10
+ const stored = window.localStorage.getItem('theme')
11
+ if (stored === 'light' || stored === 'dark' || stored === 'auto') {
12
+ return stored
13
+ }
14
+
15
+ return 'auto'
16
+ }
17
+
18
+ function applyThemeMode(mode: ThemeMode) {
19
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
20
+ const resolved = mode === 'auto' ? (prefersDark ? 'dark' : 'light') : mode
21
+
22
+ document.documentElement.classList.remove('light', 'dark')
23
+ document.documentElement.classList.add(resolved)
24
+
25
+ if (mode === 'auto') {
26
+ document.documentElement.removeAttribute('data-theme')
27
+ } else {
28
+ document.documentElement.setAttribute('data-theme', mode)
29
+ }
30
+
31
+ document.documentElement.style.colorScheme = resolved
32
+ }
33
+
34
+ export default function ThemeToggle() {
35
+ const [mode, setMode] = useState<ThemeMode>('auto')
36
+
37
+ useEffect(() => {
38
+ const initialMode = getInitialMode()
39
+ setMode(initialMode)
40
+ applyThemeMode(initialMode)
41
+ }, [])
42
+
43
+ useEffect(() => {
44
+ if (mode !== 'auto') {
45
+ return
46
+ }
47
+
48
+ const media = window.matchMedia('(prefers-color-scheme: dark)')
49
+ const onChange = () => applyThemeMode('auto')
50
+
51
+ media.addEventListener('change', onChange)
52
+ return () => {
53
+ media.removeEventListener('change', onChange)
54
+ }
55
+ }, [mode])
56
+
57
+ function toggleMode() {
58
+ const nextMode: ThemeMode =
59
+ mode === 'light' ? 'dark' : mode === 'dark' ? 'auto' : 'light'
60
+ setMode(nextMode)
61
+ applyThemeMode(nextMode)
62
+ window.localStorage.setItem('theme', nextMode)
63
+ }
64
+
65
+ const label =
66
+ mode === 'auto'
67
+ ? 'Theme mode: auto (system). Click to switch to light mode.'
68
+ : `Theme mode: ${mode}. Click to switch mode.`
69
+
70
+ return (
71
+ <button
72
+ type="button"
73
+ onClick={toggleMode}
74
+ aria-label={label}
75
+ title={label}
76
+ className="rounded-full border border-[var(--chip-line)] bg-[var(--chip-bg)] px-3 py-1.5 text-sm font-semibold text-[var(--sea-ink)] shadow-[0_8px_22px_rgba(30,90,72,0.08)] transition hover:-translate-y-0.5"
77
+ >
78
+ {mode === 'auto' ? 'Auto' : mode === 'dark' ? 'Dark' : 'Light'}
79
+ </button>
80
+ )
81
+ }
@@ -0,0 +1,4 @@
1
+ export const SITE_TITLE = 'TanStack Start'
2
+ export const SITE_DESCRIPTION =
3
+ 'A tropical, breathable app starter with full-document SSR, server functions, streaming, and type-safe routing.'
4
+ export const SITE_URL = 'https://example.com'
@@ -1,5 +1,4 @@
1
1
  <% if (!routerOnly) { ignoreFile() } %>
2
- import React from 'react'
3
2
  import ReactDOM from 'react-dom/client'
4
3
  import { RouterProvider, createRouter } from '@tanstack/react-router'
5
4
  import { routeTree } from './routeTree.gen'