@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.
- package/CHANGELOG.md +13 -0
- package/dist/config-file.js +5 -2
- package/dist/custom-add-ons/starter.js +45 -28
- package/dist/file-helpers.js +1 -0
- package/dist/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
- package/dist/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/dist/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
- package/dist/frameworks/react/add-ons/store/package.json +2 -2
- package/dist/frameworks/react/index.js +2 -2
- package/dist/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
- package/dist/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
- package/dist/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
- package/dist/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
- package/dist/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
- package/dist/frameworks/react/project/base/content-collections.ts.ejs +37 -0
- package/dist/frameworks/react/project/base/package.json +8 -1
- package/dist/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
- package/dist/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
- package/dist/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
- package/dist/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
- package/dist/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
- package/dist/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
- package/dist/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
- package/dist/frameworks/react/project/base/src/main.tsx.ejs +0 -1
- package/dist/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
- package/dist/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
- package/dist/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
- package/dist/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
- package/dist/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
- package/dist/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
- package/dist/frameworks/react/project/base/src/styles.css.ejs +268 -6
- package/dist/frameworks/react/project/base/tsconfig.json.ejs +2 -0
- package/dist/frameworks/react/project/base/vite.config.ts.ejs +2 -0
- package/dist/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/dist/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
- package/dist/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
- package/dist/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
- package/dist/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
- package/dist/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
- package/dist/frameworks.js +3 -0
- package/dist/package-json.js +1 -1
- package/dist/registry.js +21 -4
- package/dist/types/registry.d.ts +38 -0
- package/package.json +1 -1
- package/src/config-file.ts +6 -2
- package/src/custom-add-ons/starter.ts +30 -10
- package/src/file-helpers.ts +1 -0
- package/src/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
- package/src/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/src/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
- package/src/frameworks/react/add-ons/store/package.json +2 -2
- package/src/frameworks/react/index.ts +2 -2
- package/src/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
- package/src/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
- package/src/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
- package/src/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
- package/src/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
- package/src/frameworks/react/project/base/content-collections.ts.ejs +37 -0
- package/src/frameworks/react/project/base/package.json +8 -1
- package/src/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
- package/src/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
- package/src/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
- package/src/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
- package/src/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
- package/src/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
- package/src/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
- package/src/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
- package/src/frameworks/react/project/base/src/main.tsx.ejs +0 -1
- package/src/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
- package/src/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
- package/src/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
- package/src/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
- package/src/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
- package/src/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
- package/src/frameworks/react/project/base/src/styles.css.ejs +268 -6
- package/src/frameworks/react/project/base/tsconfig.json.ejs +2 -0
- package/src/frameworks/react/project/base/vite.config.ts.ejs +2 -0
- package/src/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/src/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
- package/src/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
- package/src/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
- package/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
- package/src/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
- package/src/frameworks.ts +4 -0
- package/src/package-json.ts +1 -1
- package/src/registry.ts +28 -4
- package/tests/add-ons.test.ts +4 -4
- package/tests/config-file.test.ts +3 -3
- package/tests/custom-add-ons/starter.test.ts +34 -2
- package/tests/frameworks.test.ts +24 -0
- package/tests/options.test.ts +4 -4
- package/tests/utils.test.ts +2 -2
|
@@ -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">© {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
|
-
|
|
2
|
-
<% for(const integration of integrations.filter(i => i.type === 'header-user')) { %>
|
|
3
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
</
|
|
57
|
-
</header>
|
|
32
|
+
</h2>
|
|
58
33
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
<
|
|
72
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
86
|
-
<span className="font-medium">Home</span>
|
|
77
|
+
Blog
|
|
87
78
|
</Link>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -33,9 +33,9 @@ import {
|
|
|
33
33
|
HeadContent, Scripts, <% if (hasContext) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router'
|
|
34
34
|
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
|
|
35
35
|
import { TanStackDevtools } from '@tanstack/react-devtools'
|
|
36
|
-
|
|
36
|
+
import Footer from '../components/Footer'
|
|
37
37
|
import Header from '../components/Header'
|
|
38
|
-
<%
|
|
38
|
+
<% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider' || i.type === 'devtools')) { %>
|
|
39
39
|
import <%= integration.jsName %> from '<%= relativePath(integration.path, true) %>'
|
|
40
40
|
<% } %><% if (addOnEnabled.paraglide) { %>
|
|
41
41
|
import { getLocale } from '#/paraglide/runtime'
|
|
@@ -61,6 +61,8 @@ interface MyRouterContext <% if (addOnEnabled["apollo-client"]) {%> extends Apol
|
|
|
61
61
|
<% } %>
|
|
62
62
|
}<% } %>
|
|
63
63
|
|
|
64
|
+
const THEME_INIT_SCRIPT = `(function(){try{var stored=window.localStorage.getItem('theme');var mode=(stored==='light'||stored==='dark'||stored==='auto')?stored:'auto';var prefersDark=window.matchMedia('(prefers-color-scheme: dark)').matches;var resolved=mode==='auto'?(prefersDark?'dark':'light'):mode;var root=document.documentElement;root.classList.remove('light','dark');root.classList.add(resolved);if(mode==='auto'){root.removeAttribute('data-theme')}else{root.setAttribute('data-theme',mode)}root.style.colorScheme=resolved;}catch(e){}})();`
|
|
65
|
+
|
|
64
66
|
export const Route = <% if (hasContext) { %>createRootRouteWithContext<MyRouterContext>()<% } else { %>createRootRoute<% } %>({
|
|
65
67
|
<% if (addOnEnabled.paraglide) { %>
|
|
66
68
|
beforeLoad: async () => {
|
|
@@ -96,14 +98,16 @@ export const Route = <% if (hasContext) { %>createRootRouteWithContext<MyRouterC
|
|
|
96
98
|
|
|
97
99
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
98
100
|
return (
|
|
99
|
-
|
|
101
|
+
<% if (addOnEnabled.paraglide) { %><html lang={getLocale()} suppressHydrationWarning><% } else { %><html lang="en" suppressHydrationWarning><% } %>
|
|
100
102
|
<head>
|
|
103
|
+
<script dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }} />
|
|
101
104
|
<HeadContent />
|
|
102
105
|
</head>
|
|
103
|
-
<body>
|
|
106
|
+
<body className="font-sans antialiased [overflow-wrap:anywhere] selection:bg-[rgba(79,184,178,0.24)]">
|
|
104
107
|
<% for(const integration of integrations.filter(i => i.type === 'provider')) { %><<%= integration.jsName %>>
|
|
105
|
-
<% }
|
|
106
|
-
|
|
108
|
+
<% } %><Header />
|
|
109
|
+
{children}
|
|
110
|
+
<Footer />
|
|
107
111
|
<TanStackDevtools
|
|
108
112
|
config={{
|
|
109
113
|
position: 'bottom-right',
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/about')({
|
|
4
|
+
component: About,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
function About() {
|
|
8
|
+
return (
|
|
9
|
+
<main className="page-wrap px-4 py-12">
|
|
10
|
+
<section className="island-shell rounded-2xl p-6 sm:p-8">
|
|
11
|
+
<img
|
|
12
|
+
src="/images/lagoon-about.svg"
|
|
13
|
+
alt=""
|
|
14
|
+
className="mb-6 h-56 w-full rounded-2xl object-cover"
|
|
15
|
+
/>
|
|
16
|
+
<p className="island-kicker mb-2">About</p>
|
|
17
|
+
<h1 className="display-title mb-3 text-4xl font-bold text-[var(--sea-ink)] sm:text-5xl">
|
|
18
|
+
Built for shipping fast.
|
|
19
|
+
</h1>
|
|
20
|
+
<p className="m-0 max-w-3xl text-base leading-8 text-[var(--sea-ink-soft)]">
|
|
21
|
+
TanStack Start gives you type-safe routing, server functions, and modern SSR
|
|
22
|
+
defaults so you can focus on product work instead of framework glue.
|
|
23
|
+
</p>
|
|
24
|
+
</section>
|
|
25
|
+
</main>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createFileRoute, notFound } from '@tanstack/react-router'
|
|
2
|
+
import { MDXContent } from '@content-collections/mdx/react'
|
|
3
|
+
import { allBlogs } from 'content-collections'
|
|
4
|
+
import { SITE_URL } from '#/lib/site'
|
|
5
|
+
import { MdxCallout } from '#/components/MdxCallout'
|
|
6
|
+
import { MdxMetrics } from '#/components/MdxMetrics'
|
|
7
|
+
|
|
8
|
+
export const Route = createFileRoute('/blog/$slug')({
|
|
9
|
+
loader: ({ params }) => {
|
|
10
|
+
const post = Array.from(
|
|
11
|
+
new Map(
|
|
12
|
+
[...allBlogs]
|
|
13
|
+
.sort((a, b) => new Date(b.pubDate).valueOf() - new Date(a.pubDate).valueOf())
|
|
14
|
+
.map((entry) => [entry.slug, entry]),
|
|
15
|
+
).values(),
|
|
16
|
+
).find((entry) => entry.slug === params.slug)
|
|
17
|
+
if (!post) throw notFound()
|
|
18
|
+
return post
|
|
19
|
+
},
|
|
20
|
+
head: ({ loaderData, params }) => {
|
|
21
|
+
const title = loaderData?.title ?? 'Post'
|
|
22
|
+
const description = loaderData?.description ?? ''
|
|
23
|
+
const image = loaderData?.heroImage ?? '/images/lagoon-1.svg'
|
|
24
|
+
return {
|
|
25
|
+
links: [{ rel: 'canonical', href: `${SITE_URL}/blog/${params.slug}` }],
|
|
26
|
+
meta: [
|
|
27
|
+
{ title },
|
|
28
|
+
{ name: 'description', content: description },
|
|
29
|
+
{
|
|
30
|
+
property: 'og:image',
|
|
31
|
+
content: image.startsWith('http') ? image : `${SITE_URL}${image}`,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
component: BlogPost,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
function BlogPost() {
|
|
40
|
+
const post = Route.useLoaderData()
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<main className="page-wrap px-4 pb-12 pt-16">
|
|
44
|
+
<article className="island-shell rounded-2xl p-6 sm:p-8">
|
|
45
|
+
{post.heroImage ? (
|
|
46
|
+
<img
|
|
47
|
+
src={post.heroImage}
|
|
48
|
+
alt=""
|
|
49
|
+
className="mb-6 h-64 w-full rounded-2xl object-cover"
|
|
50
|
+
/>
|
|
51
|
+
) : null}
|
|
52
|
+
<p className="island-kicker mb-2">Post</p>
|
|
53
|
+
<h1 className="display-title mb-3 text-4xl font-bold text-[var(--sea-ink)] sm:text-5xl">
|
|
54
|
+
{post.title}
|
|
55
|
+
</h1>
|
|
56
|
+
<p className="mb-6 text-sm text-[var(--sea-ink-soft)]">
|
|
57
|
+
{new Date(post.pubDate).toLocaleDateString()}
|
|
58
|
+
</p>
|
|
59
|
+
<div
|
|
60
|
+
className="prose prose-slate prose-headings:text-[var(--sea-ink)] prose-p:text-[var(--sea-ink-soft)] prose-li:text-[var(--sea-ink-soft)] prose-ul:text-[var(--sea-ink-soft)] prose-ol:text-[var(--sea-ink-soft)] prose-strong:text-[var(--sea-ink)] prose-a:text-[var(--lagoon-deep)] max-w-none"
|
|
61
|
+
>
|
|
62
|
+
{post.mdx ? (
|
|
63
|
+
<MDXContent code={post.mdx} components={{ MdxCallout, MdxMetrics }} />
|
|
64
|
+
) : (
|
|
65
|
+
<div dangerouslySetInnerHTML={{ __html: post.html ?? '' }} />
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
</article>
|
|
69
|
+
</main>
|
|
70
|
+
)
|
|
71
|
+
}
|