this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chapter redesign 67

+433 -360
+10
src/components/blog-table-split-column.tsx
··· 1 + function BlogTableSplitColumn({ blog, table }: { blog: React.ReactNode; table: React.ReactNode }) { 2 + return ( 3 + <div className="grid gap-8 xl:grid-cols-[minmax(0,0.9fr)_minmax(34rem,1.1fr)] xl:items-start"> 4 + {blog} 5 + <div className="min-w-0">{table}</div> 6 + </div> 7 + ); 8 + } 9 + 10 + export { BlogTableSplitColumn };
+3 -2
src/components/pagination-nav.tsx
··· 1 - import { type FileRoutesByPath, Link } from "@tanstack/react-router"; 1 + import { Link } from "@tanstack/react-router"; 2 + import type { FileRoutesByTo } from "~/routeTree.gen"; 2 3 import { cn } from "~/lib/utils"; 3 4 4 5 interface PaginationNavProps { ··· 6 7 prevOffset: number | null; 7 8 nextOffset: number | null; 8 9 search?: Record<string, unknown>; 9 - to: keyof FileRoutesByPath; 10 + to: keyof FileRoutesByTo; 10 11 } 11 12 12 13 export function PaginationNav(props: PaginationNavProps) {
-40
src/components/strategy-page-layout.tsx
··· 1 - import { ChapterPager } from "~/components/chapter-navigation"; 2 - import { SectionHeader } from "~/components/console/section-header"; 3 - 4 - interface StrategyPageLayoutProps { 5 - sidebar?: React.ReactNode; 6 - children: React.ReactNode; 7 - } 8 - 9 - export function StrategyPageLayout({ sidebar, children }: StrategyPageLayoutProps) { 10 - return ( 11 - <> 12 - <div className="grid gap-8 xl:grid-cols-[minmax(0,0.9fr)_minmax(34rem,1.1fr)] xl:items-start"> 13 - {sidebar} 14 - <div className="min-w-0">{children}</div> 15 - </div> 16 - <ChapterPager /> 17 - </> 18 - ); 19 - } 20 - 21 - interface StrategyChapterLayoutProps { 22 - children: React.ReactNode; 23 - headerSubtitle?: string; 24 - headerTitle: string; 25 - sidebar?: React.ReactNode; 26 - } 27 - 28 - export function StrategyChapterLayout(props: StrategyChapterLayoutProps) { 29 - const { children, headerSubtitle, headerTitle, sidebar } = props; 30 - 31 - return ( 32 - <main className="min-h-screen bg-(--bg-primary) p-6"> 33 - <div className="max-w-7xl mx-auto"> 34 - <SectionHeader title={headerTitle} subtitle={headerSubtitle} /> 35 - 36 - <StrategyPageLayout sidebar={sidebar}>{children}</StrategyPageLayout> 37 - </div> 38 - </main> 39 - ); 40 - }
+154 -125
src/routeTree.gen.ts
··· 9 9 // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 10 11 11 import { Route as rootRouteImport } from './routes/__root' 12 - import { Route as PreloadingRouteImport } from './routes/preloading' 13 - import { Route as PaginationRouteImport } from './routes/pagination' 14 - import { Route as LiveQueryFiltersRouteImport } from './routes/live-query-filters' 15 - import { Route as LiveQueryRouteImport } from './routes/live-query' 16 - import { Route as IntentPreloadingRouteImport } from './routes/intent-preloading' 17 - import { Route as FiltersRouteImport } from './routes/filters' 18 - import { Route as DebouncedPreloadFiltersRouteImport } from './routes/debounced-preload-filters' 19 - import { Route as BasicRouteImport } from './routes/basic' 12 + import { Route as ChaptersRouteImport } from './routes/_chapters' 20 13 import { Route as IndexRouteImport } from './routes/index' 14 + import { Route as ChaptersPreloadingRouteImport } from './routes/_chapters.preloading' 15 + import { Route as ChaptersPaginationRouteImport } from './routes/_chapters.pagination' 16 + import { Route as ChaptersLiveQueryFiltersRouteImport } from './routes/_chapters.live-query-filters' 17 + import { Route as ChaptersLiveQueryRouteImport } from './routes/_chapters.live-query' 18 + import { Route as ChaptersIntentPreloadingRouteImport } from './routes/_chapters.intent-preloading' 19 + import { Route as ChaptersFiltersRouteImport } from './routes/_chapters.filters' 20 + import { Route as ChaptersDebouncedPreloadFiltersRouteImport } from './routes/_chapters.debounced-preload-filters' 21 + import { Route as ChaptersBasicRouteImport } from './routes/_chapters.basic' 21 22 import { Route as ApiShapesTypesRouteImport } from './routes/api/shapes/types' 22 23 import { Route as ApiShapesPokemonTypesRouteImport } from './routes/api/shapes/pokemon-types' 23 24 import { Route as ApiShapesPokemonRouteImport } from './routes/api/shapes/pokemon' 24 25 25 - const PreloadingRoute = PreloadingRouteImport.update({ 26 + const ChaptersRoute = ChaptersRouteImport.update({ 27 + id: '/_chapters', 28 + getParentRoute: () => rootRouteImport, 29 + } as any) 30 + const IndexRoute = IndexRouteImport.update({ 31 + id: '/', 32 + path: '/', 33 + getParentRoute: () => rootRouteImport, 34 + } as any) 35 + const ChaptersPreloadingRoute = ChaptersPreloadingRouteImport.update({ 26 36 id: '/preloading', 27 37 path: '/preloading', 28 - getParentRoute: () => rootRouteImport, 38 + getParentRoute: () => ChaptersRoute, 29 39 } as any) 30 - const PaginationRoute = PaginationRouteImport.update({ 40 + const ChaptersPaginationRoute = ChaptersPaginationRouteImport.update({ 31 41 id: '/pagination', 32 42 path: '/pagination', 33 - getParentRoute: () => rootRouteImport, 34 - } as any) 35 - const LiveQueryFiltersRoute = LiveQueryFiltersRouteImport.update({ 36 - id: '/live-query-filters', 37 - path: '/live-query-filters', 38 - getParentRoute: () => rootRouteImport, 43 + getParentRoute: () => ChaptersRoute, 39 44 } as any) 40 - const LiveQueryRoute = LiveQueryRouteImport.update({ 45 + const ChaptersLiveQueryFiltersRoute = 46 + ChaptersLiveQueryFiltersRouteImport.update({ 47 + id: '/live-query-filters', 48 + path: '/live-query-filters', 49 + getParentRoute: () => ChaptersRoute, 50 + } as any) 51 + const ChaptersLiveQueryRoute = ChaptersLiveQueryRouteImport.update({ 41 52 id: '/live-query', 42 53 path: '/live-query', 43 - getParentRoute: () => rootRouteImport, 54 + getParentRoute: () => ChaptersRoute, 44 55 } as any) 45 - const IntentPreloadingRoute = IntentPreloadingRouteImport.update({ 46 - id: '/intent-preloading', 47 - path: '/intent-preloading', 48 - getParentRoute: () => rootRouteImport, 49 - } as any) 50 - const FiltersRoute = FiltersRouteImport.update({ 56 + const ChaptersIntentPreloadingRoute = 57 + ChaptersIntentPreloadingRouteImport.update({ 58 + id: '/intent-preloading', 59 + path: '/intent-preloading', 60 + getParentRoute: () => ChaptersRoute, 61 + } as any) 62 + const ChaptersFiltersRoute = ChaptersFiltersRouteImport.update({ 51 63 id: '/filters', 52 64 path: '/filters', 53 - getParentRoute: () => rootRouteImport, 54 - } as any) 55 - const DebouncedPreloadFiltersRoute = DebouncedPreloadFiltersRouteImport.update({ 56 - id: '/debounced-preload-filters', 57 - path: '/debounced-preload-filters', 58 - getParentRoute: () => rootRouteImport, 65 + getParentRoute: () => ChaptersRoute, 59 66 } as any) 60 - const BasicRoute = BasicRouteImport.update({ 67 + const ChaptersDebouncedPreloadFiltersRoute = 68 + ChaptersDebouncedPreloadFiltersRouteImport.update({ 69 + id: '/debounced-preload-filters', 70 + path: '/debounced-preload-filters', 71 + getParentRoute: () => ChaptersRoute, 72 + } as any) 73 + const ChaptersBasicRoute = ChaptersBasicRouteImport.update({ 61 74 id: '/basic', 62 75 path: '/basic', 63 - getParentRoute: () => rootRouteImport, 64 - } as any) 65 - const IndexRoute = IndexRouteImport.update({ 66 - id: '/', 67 - path: '/', 68 - getParentRoute: () => rootRouteImport, 76 + getParentRoute: () => ChaptersRoute, 69 77 } as any) 70 78 const ApiShapesTypesRoute = ApiShapesTypesRouteImport.update({ 71 79 id: '/api/shapes/types', ··· 85 93 86 94 export interface FileRoutesByFullPath { 87 95 '/': typeof IndexRoute 88 - '/basic': typeof BasicRoute 89 - '/debounced-preload-filters': typeof DebouncedPreloadFiltersRoute 90 - '/filters': typeof FiltersRoute 91 - '/intent-preloading': typeof IntentPreloadingRoute 92 - '/live-query': typeof LiveQueryRoute 93 - '/live-query-filters': typeof LiveQueryFiltersRoute 94 - '/pagination': typeof PaginationRoute 95 - '/preloading': typeof PreloadingRoute 96 + '/basic': typeof ChaptersBasicRoute 97 + '/debounced-preload-filters': typeof ChaptersDebouncedPreloadFiltersRoute 98 + '/filters': typeof ChaptersFiltersRoute 99 + '/intent-preloading': typeof ChaptersIntentPreloadingRoute 100 + '/live-query': typeof ChaptersLiveQueryRoute 101 + '/live-query-filters': typeof ChaptersLiveQueryFiltersRoute 102 + '/pagination': typeof ChaptersPaginationRoute 103 + '/preloading': typeof ChaptersPreloadingRoute 96 104 '/api/shapes/pokemon': typeof ApiShapesPokemonRoute 97 105 '/api/shapes/pokemon-types': typeof ApiShapesPokemonTypesRoute 98 106 '/api/shapes/types': typeof ApiShapesTypesRoute 99 107 } 100 108 export interface FileRoutesByTo { 101 109 '/': typeof IndexRoute 102 - '/basic': typeof BasicRoute 103 - '/debounced-preload-filters': typeof DebouncedPreloadFiltersRoute 104 - '/filters': typeof FiltersRoute 105 - '/intent-preloading': typeof IntentPreloadingRoute 106 - '/live-query': typeof LiveQueryRoute 107 - '/live-query-filters': typeof LiveQueryFiltersRoute 108 - '/pagination': typeof PaginationRoute 109 - '/preloading': typeof PreloadingRoute 110 + '/basic': typeof ChaptersBasicRoute 111 + '/debounced-preload-filters': typeof ChaptersDebouncedPreloadFiltersRoute 112 + '/filters': typeof ChaptersFiltersRoute 113 + '/intent-preloading': typeof ChaptersIntentPreloadingRoute 114 + '/live-query': typeof ChaptersLiveQueryRoute 115 + '/live-query-filters': typeof ChaptersLiveQueryFiltersRoute 116 + '/pagination': typeof ChaptersPaginationRoute 117 + '/preloading': typeof ChaptersPreloadingRoute 110 118 '/api/shapes/pokemon': typeof ApiShapesPokemonRoute 111 119 '/api/shapes/pokemon-types': typeof ApiShapesPokemonTypesRoute 112 120 '/api/shapes/types': typeof ApiShapesTypesRoute ··· 114 122 export interface FileRoutesById { 115 123 __root__: typeof rootRouteImport 116 124 '/': typeof IndexRoute 117 - '/basic': typeof BasicRoute 118 - '/debounced-preload-filters': typeof DebouncedPreloadFiltersRoute 119 - '/filters': typeof FiltersRoute 120 - '/intent-preloading': typeof IntentPreloadingRoute 121 - '/live-query': typeof LiveQueryRoute 122 - '/live-query-filters': typeof LiveQueryFiltersRoute 123 - '/pagination': typeof PaginationRoute 124 - '/preloading': typeof PreloadingRoute 125 + '/_chapters': typeof ChaptersRouteWithChildren 126 + '/_chapters/basic': typeof ChaptersBasicRoute 127 + '/_chapters/debounced-preload-filters': typeof ChaptersDebouncedPreloadFiltersRoute 128 + '/_chapters/filters': typeof ChaptersFiltersRoute 129 + '/_chapters/intent-preloading': typeof ChaptersIntentPreloadingRoute 130 + '/_chapters/live-query': typeof ChaptersLiveQueryRoute 131 + '/_chapters/live-query-filters': typeof ChaptersLiveQueryFiltersRoute 132 + '/_chapters/pagination': typeof ChaptersPaginationRoute 133 + '/_chapters/preloading': typeof ChaptersPreloadingRoute 125 134 '/api/shapes/pokemon': typeof ApiShapesPokemonRoute 126 135 '/api/shapes/pokemon-types': typeof ApiShapesPokemonTypesRoute 127 136 '/api/shapes/types': typeof ApiShapesTypesRoute ··· 158 167 id: 159 168 | '__root__' 160 169 | '/' 161 - | '/basic' 162 - | '/debounced-preload-filters' 163 - | '/filters' 164 - | '/intent-preloading' 165 - | '/live-query' 166 - | '/live-query-filters' 167 - | '/pagination' 168 - | '/preloading' 170 + | '/_chapters' 171 + | '/_chapters/basic' 172 + | '/_chapters/debounced-preload-filters' 173 + | '/_chapters/filters' 174 + | '/_chapters/intent-preloading' 175 + | '/_chapters/live-query' 176 + | '/_chapters/live-query-filters' 177 + | '/_chapters/pagination' 178 + | '/_chapters/preloading' 169 179 | '/api/shapes/pokemon' 170 180 | '/api/shapes/pokemon-types' 171 181 | '/api/shapes/types' ··· 173 183 } 174 184 export interface RootRouteChildren { 175 185 IndexRoute: typeof IndexRoute 176 - BasicRoute: typeof BasicRoute 177 - DebouncedPreloadFiltersRoute: typeof DebouncedPreloadFiltersRoute 178 - FiltersRoute: typeof FiltersRoute 179 - IntentPreloadingRoute: typeof IntentPreloadingRoute 180 - LiveQueryRoute: typeof LiveQueryRoute 181 - LiveQueryFiltersRoute: typeof LiveQueryFiltersRoute 182 - PaginationRoute: typeof PaginationRoute 183 - PreloadingRoute: typeof PreloadingRoute 186 + ChaptersRoute: typeof ChaptersRouteWithChildren 184 187 ApiShapesPokemonRoute: typeof ApiShapesPokemonRoute 185 188 ApiShapesPokemonTypesRoute: typeof ApiShapesPokemonTypesRoute 186 189 ApiShapesTypesRoute: typeof ApiShapesTypesRoute ··· 188 191 189 192 declare module '@tanstack/react-router' { 190 193 interface FileRoutesByPath { 191 - '/preloading': { 192 - id: '/preloading' 194 + '/_chapters': { 195 + id: '/_chapters' 196 + path: '' 197 + fullPath: '/' 198 + preLoaderRoute: typeof ChaptersRouteImport 199 + parentRoute: typeof rootRouteImport 200 + } 201 + '/': { 202 + id: '/' 203 + path: '/' 204 + fullPath: '/' 205 + preLoaderRoute: typeof IndexRouteImport 206 + parentRoute: typeof rootRouteImport 207 + } 208 + '/_chapters/preloading': { 209 + id: '/_chapters/preloading' 193 210 path: '/preloading' 194 211 fullPath: '/preloading' 195 - preLoaderRoute: typeof PreloadingRouteImport 196 - parentRoute: typeof rootRouteImport 212 + preLoaderRoute: typeof ChaptersPreloadingRouteImport 213 + parentRoute: typeof ChaptersRoute 197 214 } 198 - '/pagination': { 199 - id: '/pagination' 215 + '/_chapters/pagination': { 216 + id: '/_chapters/pagination' 200 217 path: '/pagination' 201 218 fullPath: '/pagination' 202 - preLoaderRoute: typeof PaginationRouteImport 203 - parentRoute: typeof rootRouteImport 219 + preLoaderRoute: typeof ChaptersPaginationRouteImport 220 + parentRoute: typeof ChaptersRoute 204 221 } 205 - '/live-query-filters': { 206 - id: '/live-query-filters' 222 + '/_chapters/live-query-filters': { 223 + id: '/_chapters/live-query-filters' 207 224 path: '/live-query-filters' 208 225 fullPath: '/live-query-filters' 209 - preLoaderRoute: typeof LiveQueryFiltersRouteImport 210 - parentRoute: typeof rootRouteImport 226 + preLoaderRoute: typeof ChaptersLiveQueryFiltersRouteImport 227 + parentRoute: typeof ChaptersRoute 211 228 } 212 - '/live-query': { 213 - id: '/live-query' 229 + '/_chapters/live-query': { 230 + id: '/_chapters/live-query' 214 231 path: '/live-query' 215 232 fullPath: '/live-query' 216 - preLoaderRoute: typeof LiveQueryRouteImport 217 - parentRoute: typeof rootRouteImport 233 + preLoaderRoute: typeof ChaptersLiveQueryRouteImport 234 + parentRoute: typeof ChaptersRoute 218 235 } 219 - '/intent-preloading': { 220 - id: '/intent-preloading' 236 + '/_chapters/intent-preloading': { 237 + id: '/_chapters/intent-preloading' 221 238 path: '/intent-preloading' 222 239 fullPath: '/intent-preloading' 223 - preLoaderRoute: typeof IntentPreloadingRouteImport 224 - parentRoute: typeof rootRouteImport 240 + preLoaderRoute: typeof ChaptersIntentPreloadingRouteImport 241 + parentRoute: typeof ChaptersRoute 225 242 } 226 - '/filters': { 227 - id: '/filters' 243 + '/_chapters/filters': { 244 + id: '/_chapters/filters' 228 245 path: '/filters' 229 246 fullPath: '/filters' 230 - preLoaderRoute: typeof FiltersRouteImport 231 - parentRoute: typeof rootRouteImport 247 + preLoaderRoute: typeof ChaptersFiltersRouteImport 248 + parentRoute: typeof ChaptersRoute 232 249 } 233 - '/debounced-preload-filters': { 234 - id: '/debounced-preload-filters' 250 + '/_chapters/debounced-preload-filters': { 251 + id: '/_chapters/debounced-preload-filters' 235 252 path: '/debounced-preload-filters' 236 253 fullPath: '/debounced-preload-filters' 237 - preLoaderRoute: typeof DebouncedPreloadFiltersRouteImport 238 - parentRoute: typeof rootRouteImport 254 + preLoaderRoute: typeof ChaptersDebouncedPreloadFiltersRouteImport 255 + parentRoute: typeof ChaptersRoute 239 256 } 240 - '/basic': { 241 - id: '/basic' 257 + '/_chapters/basic': { 258 + id: '/_chapters/basic' 242 259 path: '/basic' 243 260 fullPath: '/basic' 244 - preLoaderRoute: typeof BasicRouteImport 245 - parentRoute: typeof rootRouteImport 246 - } 247 - '/': { 248 - id: '/' 249 - path: '/' 250 - fullPath: '/' 251 - preLoaderRoute: typeof IndexRouteImport 252 - parentRoute: typeof rootRouteImport 261 + preLoaderRoute: typeof ChaptersBasicRouteImport 262 + parentRoute: typeof ChaptersRoute 253 263 } 254 264 '/api/shapes/types': { 255 265 id: '/api/shapes/types' ··· 275 285 } 276 286 } 277 287 288 + interface ChaptersRouteChildren { 289 + ChaptersBasicRoute: typeof ChaptersBasicRoute 290 + ChaptersDebouncedPreloadFiltersRoute: typeof ChaptersDebouncedPreloadFiltersRoute 291 + ChaptersFiltersRoute: typeof ChaptersFiltersRoute 292 + ChaptersIntentPreloadingRoute: typeof ChaptersIntentPreloadingRoute 293 + ChaptersLiveQueryRoute: typeof ChaptersLiveQueryRoute 294 + ChaptersLiveQueryFiltersRoute: typeof ChaptersLiveQueryFiltersRoute 295 + ChaptersPaginationRoute: typeof ChaptersPaginationRoute 296 + ChaptersPreloadingRoute: typeof ChaptersPreloadingRoute 297 + } 298 + 299 + const ChaptersRouteChildren: ChaptersRouteChildren = { 300 + ChaptersBasicRoute: ChaptersBasicRoute, 301 + ChaptersDebouncedPreloadFiltersRoute: ChaptersDebouncedPreloadFiltersRoute, 302 + ChaptersFiltersRoute: ChaptersFiltersRoute, 303 + ChaptersIntentPreloadingRoute: ChaptersIntentPreloadingRoute, 304 + ChaptersLiveQueryRoute: ChaptersLiveQueryRoute, 305 + ChaptersLiveQueryFiltersRoute: ChaptersLiveQueryFiltersRoute, 306 + ChaptersPaginationRoute: ChaptersPaginationRoute, 307 + ChaptersPreloadingRoute: ChaptersPreloadingRoute, 308 + } 309 + 310 + const ChaptersRouteWithChildren = ChaptersRoute._addFileChildren( 311 + ChaptersRouteChildren, 312 + ) 313 + 278 314 const rootRouteChildren: RootRouteChildren = { 279 315 IndexRoute: IndexRoute, 280 - BasicRoute: BasicRoute, 281 - DebouncedPreloadFiltersRoute: DebouncedPreloadFiltersRoute, 282 - FiltersRoute: FiltersRoute, 283 - IntentPreloadingRoute: IntentPreloadingRoute, 284 - LiveQueryRoute: LiveQueryRoute, 285 - LiveQueryFiltersRoute: LiveQueryFiltersRoute, 286 - PaginationRoute: PaginationRoute, 287 - PreloadingRoute: PreloadingRoute, 316 + ChaptersRoute: ChaptersRouteWithChildren, 288 317 ApiShapesPokemonRoute: ApiShapesPokemonRoute, 289 318 ApiShapesPokemonTypesRoute: ApiShapesPokemonTypesRoute, 290 319 ApiShapesTypesRoute: ApiShapesTypesRoute,
+5
src/router.tsx
··· 82 82 interface Register { 83 83 router: ReturnType<typeof getRouter>; 84 84 } 85 + 86 + interface StaticDataRouteOption { 87 + routeTitle?: string; 88 + routeSubtitle?: string; 89 + } 85 90 }
+38
src/routes/_chapters.tsx
··· 1 + import { createFileRoute, Outlet, useMatches } from "@tanstack/react-router"; 2 + 3 + import { ChapterPager } from "~/components/chapter-navigation"; 4 + import { SectionHeader } from "~/components/console/section-header"; 5 + 6 + export const Route = createFileRoute("/_chapters")({ 7 + staticData: { 8 + routeTitle: "FILL_THIS_IN", 9 + routeSubtitle: "FILL_THIS_IN", 10 + }, 11 + component: RouteComponent, 12 + }); 13 + 14 + function RouteComponent() { 15 + const matches = useMatches(); 16 + const leaf = matches[matches.length - 1]; 17 + const routeTitle = leaf.staticData.routeTitle; 18 + const routeSubtitle = leaf.staticData.routeSubtitle; 19 + 20 + if (!routeTitle || routeTitle === "FILL_THIS_IN") { 21 + throw new Error(`Route "${leaf?.routeId}" must define staticData.routeTitle`); 22 + } 23 + 24 + if (!routeSubtitle || routeSubtitle === "FILL_THIS_IN") { 25 + throw new Error(`Route "${leaf?.routeId}" must define staticData.routeSubtitle`); 26 + } 27 + 28 + return ( 29 + <main className="min-h-screen bg-(--bg-primary) p-6"> 30 + <div className="max-w-7xl mx-auto"> 31 + <SectionHeader title={routeTitle} subtitle={routeSubtitle} /> 32 + 33 + <Outlet /> 34 + <ChapterPager /> 35 + </div> 36 + </main> 37 + ); 38 + }
+21 -16
src/routes/basic.tsx src/routes/_chapters.basic.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 5 4 import { ConsoleCard } from "~/components/console/console-card"; 6 5 import { 7 6 PokedexPagination, ··· 10 9 } from "~/components/tables/pokedex-table-section"; 11 10 import { getPokemonListQueryFn, getPokemonListQueryKey } from "~/utils/pokemon"; 12 11 import { getStrategyArticle } from "~/server/strategy-article.functions"; 12 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 13 13 14 14 const searchParamsSchema = v.object({ 15 15 offset: v.optional(v.number(), 0), 16 16 }); 17 17 18 - export const Route = createFileRoute("/basic")({ 18 + export const Route = createFileRoute("/_chapters/basic")({ 19 + staticData: { 20 + routeTitle: "01_basic", 21 + routeSubtitle: "// Baseline fetching", 22 + }, 19 23 validateSearch: searchParamsSchema, 20 24 loader: async () => { 21 25 const { Renderable: Article } = await getStrategyArticle({ ··· 31 35 const { Article } = Route.useLoaderData(); 32 36 33 37 return ( 34 - <StrategyChapterLayout 35 - headerTitle="01_basic" 36 - headerSubtitle="// No prefetching (baseline)" 37 - sidebar={Article} 38 - > 39 - <ConsoleCard className="mb-6"> 40 - <PokedexTableSection 41 - currentOffset={currentOffset} 42 - fallbackPagination={<PokedexPagination prevOffset={null} nextOffset={null} to="/basic" />} 43 - > 44 - <PokemonTableContent currentOffset={currentOffset} /> 45 - </PokedexTableSection> 46 - </ConsoleCard> 47 - </StrategyChapterLayout> 38 + <BlogTableSplitColumn 39 + blog={Article} 40 + table={ 41 + <ConsoleCard className="mb-6"> 42 + <PokedexTableSection 43 + currentOffset={currentOffset} 44 + fallbackPagination={ 45 + <PokedexPagination prevOffset={null} nextOffset={null} to="/basic" /> 46 + } 47 + > 48 + <PokemonTableContent currentOffset={currentOffset} /> 49 + </PokedexTableSection> 50 + </ConsoleCard> 51 + } 52 + /> 48 53 ); 49 54 } 50 55
+41 -36
src/routes/debounced-preload-filters.tsx src/routes/_chapters.debounced-preload-filters.tsx
··· 3 3 import { queryOptions, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; 4 4 import * as v from "valibot"; 5 5 import { FilterForm } from "~/components/filter-form"; 6 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 6 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 7 7 import { ConsoleCard } from "~/components/console/console-card"; 8 8 import { 9 9 PokedexPagination, ··· 18 18 name: v.optional(v.string(), ""), 19 19 }); 20 20 21 - export const Route = createFileRoute("/debounced-preload-filters")({ 21 + export const Route = createFileRoute("/_chapters/debounced-preload-filters")({ 22 + staticData: { 23 + routeTitle: "06_debounced", 24 + routeSubtitle: "// Advanced filter prefetch", 25 + }, 22 26 validateSearch: searchParamsSchema, 23 27 loaderDeps: ({ search }) => ({ 24 28 offset: search.offset, ··· 73 77 ); 74 78 75 79 return ( 76 - <StrategyChapterLayout 77 - headerTitle="06_debounced" 78 - headerSubtitle="// Advanced filter prefetch" 79 - sidebar={Article} 80 - > 81 - <ConsoleCard className="mb-6"> 82 - <FilterForm 83 - initialName={nameFilter} 84 - description="Preloads results while typing (debounced 100ms)" 85 - onNameChange={(newNameFilter) => debouncedNameFilter(newNameFilter)} 86 - onSubmit={(newNameFilter) => { 87 - void navigate({ 88 - search: { name: newNameFilter }, 89 - }); 90 - }} 91 - /> 92 - </ConsoleCard> 80 + <BlogTableSplitColumn 81 + blog={Article} 82 + table={ 83 + <> 84 + <ConsoleCard className="mb-6"> 85 + <FilterForm 86 + initialName={nameFilter} 87 + description="Preloads results while typing (debounced 100ms)" 88 + onNameChange={(newNameFilter) => debouncedNameFilter(newNameFilter)} 89 + onSubmit={(newNameFilter) => { 90 + void navigate({ 91 + search: { name: newNameFilter }, 92 + }); 93 + }} 94 + /> 95 + </ConsoleCard> 93 96 94 - <ConsoleCard> 95 - <PokedexTableSection 96 - currentOffset={currentOffset} 97 - nameFilter={nameFilter} 98 - fallbackPagination={ 99 - <PokedexPagination 100 - prevOffset={null} 101 - nextOffset={null} 102 - to="/debounced-preload-filters" 103 - /> 104 - } 105 - > 106 - <PokemonTableContent nameFilter={nameFilter} /> 107 - </PokedexTableSection> 108 - </ConsoleCard> 109 - </StrategyChapterLayout> 97 + <ConsoleCard> 98 + <PokedexTableSection 99 + currentOffset={currentOffset} 100 + nameFilter={nameFilter} 101 + fallbackPagination={ 102 + <PokedexPagination 103 + prevOffset={null} 104 + nextOffset={null} 105 + to="/debounced-preload-filters" 106 + /> 107 + } 108 + > 109 + <PokemonTableContent nameFilter={nameFilter} /> 110 + </PokedexTableSection> 111 + </ConsoleCard> 112 + </> 113 + } 114 + /> 110 115 ); 111 116 } 112 117 113 118 function PokemonTableContent({ nameFilter }: { nameFilter: string }) { 114 - const { pokemonListOptions } = useRouteContext({ from: "/debounced-preload-filters" }); 119 + const { pokemonListOptions } = useRouteContext({ from: "/_chapters/debounced-preload-filters" }); 115 120 const { data } = useSuspenseQuery(pokemonListOptions); 116 121 const filteredPokemon = data.pokemon; 117 122
+35 -30
src/routes/filters.tsx src/routes/_chapters.filters.tsx
··· 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 4 import { FilterForm } from "~/components/filter-form"; 5 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 5 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 6 6 import { ConsoleCard } from "~/components/console/console-card"; 7 7 import { 8 8 PokedexPagination, ··· 17 17 name: v.optional(v.string(), ""), 18 18 }); 19 19 20 - export const Route = createFileRoute("/filters")({ 20 + export const Route = createFileRoute("/_chapters/filters")({ 21 + staticData: { 22 + routeTitle: "05_filters", 23 + routeSubtitle: "// Search with prefetch", 24 + }, 21 25 validateSearch: searchParamsSchema, 22 26 loaderDeps: ({ search }) => ({ 23 27 offset: search.offset, ··· 51 55 const navigate = Route.useNavigate(); 52 56 53 57 return ( 54 - <StrategyChapterLayout 55 - headerTitle="05_filters" 56 - headerSubtitle="// Search with prefetch" 57 - sidebar={Article} 58 - > 59 - <ConsoleCard className="mb-6"> 60 - <FilterForm 61 - initialName={nameFilter} 62 - onSubmit={(newNameFilter) => { 63 - void navigate({ 64 - search: { name: newNameFilter }, 65 - }); 66 - }} 67 - /> 68 - </ConsoleCard> 58 + <BlogTableSplitColumn 59 + blog={Article} 60 + table={ 61 + <> 62 + <ConsoleCard className="mb-6"> 63 + <FilterForm 64 + initialName={nameFilter} 65 + onSubmit={(newNameFilter) => { 66 + void navigate({ 67 + search: { name: newNameFilter }, 68 + }); 69 + }} 70 + /> 71 + </ConsoleCard> 69 72 70 - <ConsoleCard> 71 - <PokedexTableSection 72 - currentOffset={currentOffset} 73 - nameFilter={nameFilter} 74 - fallbackPagination={ 75 - <PokedexPagination prevOffset={null} nextOffset={null} to="/filters" /> 76 - } 77 - > 78 - <PokemonTableContent nameFilter={nameFilter} /> 79 - </PokedexTableSection> 80 - </ConsoleCard> 81 - </StrategyChapterLayout> 73 + <ConsoleCard> 74 + <PokedexTableSection 75 + currentOffset={currentOffset} 76 + nameFilter={nameFilter} 77 + fallbackPagination={ 78 + <PokedexPagination prevOffset={null} nextOffset={null} to="/filters" /> 79 + } 80 + > 81 + <PokemonTableContent nameFilter={nameFilter} /> 82 + </PokedexTableSection> 83 + </ConsoleCard> 84 + </> 85 + } 86 + /> 82 87 ); 83 88 } 84 89 85 90 function PokemonTableContent({ nameFilter }: { nameFilter: string }) { 86 - const { pokemonListOptions } = useRouteContext({ from: "/filters" }); 91 + const { pokemonListOptions } = useRouteContext({ from: "/_chapters/filters" }); 87 92 const { data } = useSuspenseQuery(pokemonListOptions); 88 93 const filteredPokemon = data.pokemon; 89 94
+22 -19
src/routes/intent-preloading.tsx src/routes/_chapters.intent-preloading.tsx
··· 1 1 import { createFileRoute, useRouteContext } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 4 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 5 import { ConsoleCard } from "~/components/console/console-card"; 6 6 import { 7 7 PokedexPagination, ··· 15 15 offset: v.optional(v.number(), 0), 16 16 }); 17 17 18 - export const Route = createFileRoute("/intent-preloading")({ 18 + export const Route = createFileRoute("/_chapters/intent-preloading")({ 19 + staticData: { 20 + routeTitle: "03_intent-preloading", 21 + routeSubtitle: "// Hover-based prefetch", 22 + }, 19 23 validateSearch: searchParamsSchema, 20 24 loaderDeps: ({ search }) => ({ 21 25 offset: search.offset, ··· 47 51 const { Article } = Route.useLoaderData(); 48 52 49 53 return ( 50 - <StrategyChapterLayout 51 - headerTitle="03_intent-preloading" 52 - headerSubtitle="// Hover-based prefetch" 53 - sidebar={Article} 54 - > 55 - <ConsoleCard className="mb-6"> 56 - <PokedexTableSection 57 - currentOffset={currentOffset} 58 - fallbackPagination={ 59 - <PokedexPagination prevOffset={null} nextOffset={null} to="/intent-preloading" /> 60 - } 61 - > 62 - <PokemonTableContent /> 63 - </PokedexTableSection> 64 - </ConsoleCard> 65 - </StrategyChapterLayout> 54 + <BlogTableSplitColumn 55 + blog={Article} 56 + table={ 57 + <ConsoleCard className="mb-6"> 58 + <PokedexTableSection 59 + currentOffset={currentOffset} 60 + fallbackPagination={ 61 + <PokedexPagination prevOffset={null} nextOffset={null} to="/intent-preloading" /> 62 + } 63 + > 64 + <PokemonTableContent /> 65 + </PokedexTableSection> 66 + </ConsoleCard> 67 + } 68 + /> 66 69 ); 67 70 } 68 71 69 72 function PokemonTableContent() { 70 - const { pokemonListOptions } = useRouteContext({ from: "/intent-preloading" }); 73 + const { pokemonListOptions } = useRouteContext({ from: "/_chapters/intent-preloading" }); 71 74 const { data } = useSuspenseQuery(pokemonListOptions); 72 75 73 76 return (
+39 -36
src/routes/live-query-filters.tsx src/routes/_chapters.live-query-filters.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { useLiveSuspenseQuery, eq, ilike, toArray } from "@tanstack/react-db"; 3 3 import * as v from "valibot"; 4 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 4 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 5 import { ConsoleCard } from "~/components/console/console-card"; 6 6 import { FilterForm } from "~/components/filter-form"; 7 7 import { ··· 26 26 name: v.optional(v.string(), ""), 27 27 }); 28 28 29 - export const Route = createFileRoute("/live-query-filters")({ 29 + export const Route = createFileRoute("/_chapters/live-query-filters")({ 30 + staticData: { 31 + routeTitle: "08_live-query-filters", 32 + routeSubtitle: "// Electric SQL live search", 33 + }, 30 34 ssr: false, 31 35 validateSearch: searchParamsSchema, 32 36 loader: async () => { ··· 44 48 const navigate = Route.useNavigate(); 45 49 46 50 return ( 47 - <StrategyChapterLayout 48 - headerTitle="08_live-query-filters" 49 - headerSubtitle="// Electric SQL live search" 50 - sidebar={Article} 51 - > 52 - <div> 53 - <ConsoleCard className="mb-6"> 54 - <FilterForm 55 - initialName={nameFilter} 56 - onSubmit={(newNameFilter) => { 57 - void navigate({ 58 - search: { offset: 0, name: newNameFilter }, 59 - }); 60 - }} 61 - /> 62 - </ConsoleCard> 51 + <BlogTableSplitColumn 52 + blog={Article} 53 + table={ 54 + <> 55 + <ConsoleCard className="mb-6"> 56 + <FilterForm 57 + initialName={nameFilter} 58 + onSubmit={(newNameFilter) => { 59 + void navigate({ 60 + search: { offset: 0, name: newNameFilter }, 61 + }); 62 + }} 63 + /> 64 + </ConsoleCard> 63 65 64 - <ConsoleCard> 65 - <PokedexTableSection 66 - currentOffset={currentOffset} 67 - nameFilter={nameFilter} 68 - fallbackPagination={ 69 - <PokedexPagination 70 - nameFilter={nameFilter} 71 - nextOffset={null} 72 - prevOffset={null} 73 - to="/live-query-filters" 74 - /> 75 - } 76 - > 77 - <PokemonTableContent currentOffset={currentOffset} nameFilter={nameFilter} /> 78 - </PokedexTableSection> 79 - </ConsoleCard> 80 - </div> 81 - </StrategyChapterLayout> 66 + <ConsoleCard> 67 + <PokedexTableSection 68 + currentOffset={currentOffset} 69 + nameFilter={nameFilter} 70 + fallbackPagination={ 71 + <PokedexPagination 72 + nameFilter={nameFilter} 73 + nextOffset={null} 74 + prevOffset={null} 75 + to="/live-query-filters" 76 + /> 77 + } 78 + > 79 + <PokemonTableContent currentOffset={currentOffset} nameFilter={nameFilter} /> 80 + </PokedexTableSection> 81 + </ConsoleCard> 82 + </> 83 + } 84 + /> 82 85 ); 83 86 } 84 87
+21 -18
src/routes/live-query.tsx src/routes/_chapters.live-query.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { useLiveSuspenseQuery, eq, toArray } from "@tanstack/react-db"; 3 3 import * as v from "valibot"; 4 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 4 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 5 import { ConsoleCard } from "~/components/console/console-card"; 6 6 import { 7 7 PokedexPagination, ··· 20 20 offset: v.optional(v.number(), 0), 21 21 }); 22 22 23 - export const Route = createFileRoute("/live-query")({ 23 + export const Route = createFileRoute("/_chapters/live-query")({ 24 + staticData: { 25 + routeTitle: "07_live-query", 26 + routeSubtitle: "// Electric SQL synced collection", 27 + }, 24 28 ssr: false, 25 29 validateSearch: searchParamsSchema, 26 30 loader: async () => { ··· 37 41 const { Article } = Route.useLoaderData(); 38 42 39 43 return ( 40 - <StrategyChapterLayout 41 - headerTitle="07_live-query" 42 - headerSubtitle="// Electric SQL synced collection" 43 - sidebar={Article} 44 - > 45 - <ConsoleCard className="mb-6"> 46 - <PokedexTableSection 47 - currentOffset={currentOffset} 48 - fallbackPagination={ 49 - <PokedexPagination prevOffset={null} nextOffset={null} to="/live-query" /> 50 - } 51 - > 52 - <PokemonTableContent currentOffset={currentOffset} /> 53 - </PokedexTableSection> 54 - </ConsoleCard> 55 - </StrategyChapterLayout> 44 + <BlogTableSplitColumn 45 + blog={Article} 46 + table={ 47 + <ConsoleCard className="mb-6"> 48 + <PokedexTableSection 49 + currentOffset={currentOffset} 50 + fallbackPagination={ 51 + <PokedexPagination prevOffset={null} nextOffset={null} to="/live-query" /> 52 + } 53 + > 54 + <PokemonTableContent currentOffset={currentOffset} /> 55 + </PokedexTableSection> 56 + </ConsoleCard> 57 + } 58 + /> 56 59 ); 57 60 } 58 61
+22 -19
src/routes/pagination.tsx src/routes/_chapters.pagination.tsx
··· 1 1 import { createFileRoute, useRouteContext } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 4 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 5 import { ConsoleCard } from "~/components/console/console-card"; 6 6 import { 7 7 PokedexPagination, ··· 15 15 offset: v.optional(v.number(), 0), 16 16 }); 17 17 18 - export const Route = createFileRoute("/pagination")({ 18 + export const Route = createFileRoute("/_chapters/pagination")({ 19 + staticData: { 20 + routeTitle: "04_pagination", 21 + routeSubtitle: "// Preloading next/prev pages", 22 + }, 19 23 validateSearch: searchParamsSchema, 20 24 loaderDeps: ({ search }) => ({ 21 25 offset: search.offset, ··· 47 51 const { Article } = Route.useLoaderData(); 48 52 49 53 return ( 50 - <StrategyChapterLayout 51 - headerTitle="04_pagination" 52 - headerSubtitle="// Preloading next/prev pages" 53 - sidebar={Article} 54 - > 55 - <ConsoleCard className="mb-6"> 56 - <PokedexTableSection 57 - currentOffset={currentOffset} 58 - fallbackPagination={ 59 - <PokedexPagination prevOffset={null} nextOffset={null} to="/pagination" /> 60 - } 61 - > 62 - <PokemonTableContent /> 63 - </PokedexTableSection> 64 - </ConsoleCard> 65 - </StrategyChapterLayout> 54 + <BlogTableSplitColumn 55 + blog={Article} 56 + table={ 57 + <ConsoleCard className="mb-6"> 58 + <PokedexTableSection 59 + currentOffset={currentOffset} 60 + fallbackPagination={ 61 + <PokedexPagination prevOffset={null} nextOffset={null} to="/pagination" /> 62 + } 63 + > 64 + <PokemonTableContent /> 65 + </PokedexTableSection> 66 + </ConsoleCard> 67 + } 68 + /> 66 69 ); 67 70 } 68 71 69 72 function PokemonTableContent() { 70 - const { pokemonListOptions } = useRouteContext({ from: "/pagination" }); 73 + const { pokemonListOptions } = useRouteContext({ from: "/_chapters/pagination" }); 71 74 const { data } = useSuspenseQuery(pokemonListOptions); 72 75 73 76 return (
+22 -19
src/routes/preloading.tsx src/routes/_chapters.preloading.tsx
··· 1 1 import { createFileRoute, useRouteContext } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { StrategyChapterLayout } from "~/components/strategy-page-layout"; 4 + import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 5 import { ConsoleCard } from "~/components/console/console-card"; 6 6 import { 7 7 PokedexPagination, ··· 15 15 offset: v.optional(v.number(), 0), 16 16 }); 17 17 18 - export const Route = createFileRoute("/preloading")({ 18 + export const Route = createFileRoute("/_chapters/preloading")({ 19 + staticData: { 20 + routeTitle: "02_preloading", 21 + routeSubtitle: "// Route-level prefetch", 22 + }, 19 23 validateSearch: searchParamsSchema, 20 24 loaderDeps: ({ search }) => ({ 21 25 offset: search.offset, ··· 47 51 const { Article } = Route.useLoaderData(); 48 52 49 53 return ( 50 - <StrategyChapterLayout 51 - headerTitle="02_preloading" 52 - headerSubtitle="// Route-level prefetch" 53 - sidebar={Article} 54 - > 55 - <ConsoleCard className="mb-6"> 56 - <PokedexTableSection 57 - currentOffset={currentOffset} 58 - fallbackPagination={ 59 - <PokedexPagination prevOffset={null} nextOffset={null} to="/preloading" /> 60 - } 61 - > 62 - <PokemonTableContent /> 63 - </PokedexTableSection> 64 - </ConsoleCard> 65 - </StrategyChapterLayout> 54 + <BlogTableSplitColumn 55 + blog={Article} 56 + table={ 57 + <ConsoleCard className="mb-6"> 58 + <PokedexTableSection 59 + currentOffset={currentOffset} 60 + fallbackPagination={ 61 + <PokedexPagination prevOffset={null} nextOffset={null} to="/preloading" /> 62 + } 63 + > 64 + <PokemonTableContent /> 65 + </PokedexTableSection> 66 + </ConsoleCard> 67 + } 68 + /> 66 69 ); 67 70 } 68 71 69 72 function PokemonTableContent() { 70 - const { pokemonListOptions } = useRouteContext({ from: "/preloading" }); 73 + const { pokemonListOptions } = useRouteContext({ from: "/_chapters/preloading" }); 71 74 const { data } = useSuspenseQuery(pokemonListOptions); 72 75 73 76 return (