this repo has no description
0
fork

Configure Feed

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

a

+117 -72
+3 -1
package.json
··· 21 21 "@tanstack/react-router-devtools": "^1.114.3", 22 22 "@tanstack/react-table": "^8.21.3", 23 23 "@tanstack/router-plugin": "^1.114.3", 24 + "@total-typescript/ts-reset": "^0.6.1", 24 25 "class-variance-authority": "^0.7.1", 25 26 "clsx": "^2.1.1", 26 27 "lucide-react": "^0.476.0", ··· 30 31 "react-dom": "^19.0.0", 31 32 "tailwind-merge": "^3.0.2", 32 33 "tailwindcss": "^4.0.6", 33 - "tailwindcss-animate": "^1.0.7" 34 + "tailwindcss-animate": "^1.0.7", 35 + "valibot": "^1.1.0" 34 36 }, 35 37 "devDependencies": { 36 38 "@biomejs/biome": "1.9.4",
+23
pnpm-lock.yaml
··· 32 32 '@tanstack/router-plugin': 33 33 specifier: ^1.114.3 34 34 version: 1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.4(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)) 35 + '@total-typescript/ts-reset': 36 + specifier: ^0.6.1 37 + version: 0.6.1 35 38 class-variance-authority: 36 39 specifier: ^0.7.1 37 40 version: 0.7.1 ··· 62 65 tailwindcss-animate: 63 66 specifier: ^1.0.7 64 67 version: 1.0.7(tailwindcss@4.1.5) 68 + valibot: 69 + specifier: ^1.1.0 70 + version: 1.1.0(typescript@5.8.3) 65 71 devDependencies: 66 72 '@biomejs/biome': 67 73 specifier: 1.9.4 ··· 787 793 '@types/react-dom': 788 794 optional: true 789 795 796 + '@total-typescript/ts-reset@0.6.1': 797 + resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==} 798 + 790 799 '@types/aria-query@5.0.4': 791 800 resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} 792 801 ··· 1435 1444 peerDependencies: 1436 1445 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 1437 1446 1447 + valibot@1.1.0: 1448 + resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} 1449 + peerDependencies: 1450 + typescript: '>=5' 1451 + peerDependenciesMeta: 1452 + typescript: 1453 + optional: true 1454 + 1438 1455 vite-node@3.1.2: 1439 1456 resolution: {integrity: sha512-/8iMryv46J3aK13iUXsei5G/A3CUlW4665THCPS+K8xAaqrVWiGB4RfXMQXCLjpK9P2eK//BczrVkn5JLAk6DA==} 1440 1457 engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} ··· 2123 2140 '@types/react': 19.1.2 2124 2141 '@types/react-dom': 19.1.3(@types/react@19.1.2) 2125 2142 2143 + '@total-typescript/ts-reset@0.6.1': {} 2144 + 2126 2145 '@types/aria-query@5.0.4': {} 2127 2146 2128 2147 '@types/babel__core@7.20.5': ··· 2743 2762 use-sync-external-store@1.5.0(react@19.1.0): 2744 2763 dependencies: 2745 2764 react: 19.1.0 2765 + 2766 + valibot@1.1.0(typescript@5.8.3): 2767 + optionalDependencies: 2768 + typescript: 5.8.3 2746 2769 2747 2770 vite-node@3.1.2(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4): 2748 2771 dependencies:
+50 -50
src/components/ui/button.tsx
··· 1 - import * as React from "react" 2 - import { Slot } from "@radix-ui/react-slot" 3 - import { cva, type VariantProps } from "class-variance-authority" 1 + import { Slot } from "@radix-ui/react-slot"; 2 + import { type VariantProps, cva } from "class-variance-authority"; 3 + import type * as React from "react"; 4 4 5 - import { cn } from "~/lib/utils" 5 + import { cn } from "~/lib/utils"; 6 6 7 7 const buttonVariants = cva( 8 - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 - { 10 - variants: { 11 - variant: { 12 - default: 13 - "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 - destructive: 15 - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 - outline: 17 - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 - secondary: 19 - "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 - ghost: 21 - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 22 - link: "text-primary underline-offset-4 hover:underline", 23 - }, 24 - size: { 25 - default: "h-9 px-4 py-2 has-[>svg]:px-3", 26 - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 27 - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 - icon: "size-9", 29 - }, 30 - }, 31 - defaultVariants: { 32 - variant: "default", 33 - size: "default", 34 - }, 35 - } 36 - ) 8 + `inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive`, 9 + { 10 + variants: { 11 + variant: { 12 + default: 13 + "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 + destructive: 15 + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 + outline: 17 + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 + secondary: 19 + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 + ghost: 21 + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 22 + link: "text-primary underline-offset-4 hover:underline", 23 + }, 24 + size: { 25 + default: "h-9 px-4 py-2 has-[>svg]:px-3", 26 + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 27 + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 + icon: "size-9", 29 + }, 30 + }, 31 + defaultVariants: { 32 + variant: "default", 33 + size: "default", 34 + }, 35 + }, 36 + ); 37 37 38 38 function Button({ 39 - className, 40 - variant, 41 - size, 42 - asChild = false, 43 - ...props 39 + className, 40 + variant, 41 + size, 42 + asChild = false, 43 + ...props 44 44 }: React.ComponentProps<"button"> & 45 - VariantProps<typeof buttonVariants> & { 46 - asChild?: boolean 47 - }) { 48 - const Comp = asChild ? Slot : "button" 45 + VariantProps<typeof buttonVariants> & { 46 + asChild?: boolean; 47 + }) { 48 + const Comp = asChild ? Slot : "button"; 49 49 50 - return ( 51 - <Comp 52 - data-slot="button" 53 - className={cn(buttonVariants({ variant, size, className }))} 54 - {...props} 55 - /> 56 - ) 50 + return ( 51 + <Comp 52 + data-slot="button" 53 + className={cn(buttonVariants({ variant, size, className }))} 54 + {...props} 55 + /> 56 + ); 57 57 } 58 58 59 - export { Button, buttonVariants } 59 + export { Button, buttonVariants };
+2
src/reset.d.ts
··· 1 + // Do not add any other lines of code to this file! 2 + import "@total-typescript/ts-reset";
+39 -21
src/routes/basic.tsx
··· 1 1 import { useQuery } from "@tanstack/react-query"; 2 - import { createFileRoute } from "@tanstack/react-router"; 2 + import { Link, createFileRoute } from "@tanstack/react-router"; 3 + import { buttonVariants } from "~/components/ui/button"; 4 + 5 + import { useMemo } from "react"; 6 + import * as v from "valibot"; 3 7 import { 4 8 Table, 5 9 TableBody, ··· 8 12 TableHeader, 9 13 TableRow, 10 14 } from "~/components/ui/table"; 15 + import { cn } from "~/lib/utils"; 11 16 import { $pokeApiClient } from "../data/client"; 12 - import { useMemo, useState } from "react"; 13 17 14 18 const POKEMON_LIMIT = 20; 15 19 16 20 const matchPokemonIdExp = /\/api\/v2\/pokemon\/(\d+)\/?/; 17 21 22 + const searchParamsSchema = v.object({ 23 + offset: v.optional(v.number(), 0), 24 + }); 25 + 18 26 export const Route = createFileRoute("/basic")({ 27 + validateSearch: searchParamsSchema, 19 28 component: RouteComponent, 20 29 }); 21 30 ··· 24 33 url: string; 25 34 } 26 35 27 - function RouteComponent() { 36 + function RouteComponent() { 37 + const { offset: currentOffset } = Route.useSearch(); 28 38 29 - const [currentOffset, setCurrentOffset] = useState<number | undefined>(undefined); 30 39 const { data, error } = useQuery( 31 40 $pokeApiClient.queryOptions("get", "/api/v2/pokemon/", { 32 41 params: { 33 42 query: { 34 43 limit: POKEMON_LIMIT, 35 - offset: currentOffset ?? undefined, 44 + offset: currentOffset, 36 45 }, 37 46 }, 38 47 }), 39 48 ); 40 49 41 50 const previousOffset = useMemo(() => { 42 - if(data?.previous == null) { 51 + if (data?.previous == null) { 43 52 return null; 44 - } 53 + } 45 54 46 55 return new URL(data.previous).searchParams.get("offset") ?? null; 47 56 }, [data?.previous]); 48 57 49 58 const nextOffset = useMemo(() => { 50 - if(data?.next == null) { 59 + if (data?.next == null) { 51 60 return null; 52 61 } 53 62 ··· 59 68 return ( 60 69 <div className="p-4"> 61 70 <h1 className="text-2xl font-bold mb-4"> 62 - National Pokédex: Pokémon {currentOffset + 1}-{currentOffset + POKEMON_LIMIT} 71 + National Pokédex: Pokémon {currentOffset + 1}- 72 + {currentOffset + POKEMON_LIMIT} 63 73 </h1> 64 74 <Table> 65 75 <TableHeader> ··· 72 82 <TableBody> 73 83 {results.map((pokemon: PokemonListResult, idx: number) => ( 74 84 <TableRow key={pokemon.name}> 75 - <TableCell>{pokemon.url.match(matchPokemonIdExp)?.[1]}</TableCell> 85 + <TableCell> 86 + {pokemon.url.match(matchPokemonIdExp)?.[1]} 87 + </TableCell> 76 88 <TableCell className="capitalize">{pokemon.name}</TableCell> 77 89 <TableCell> 78 90 <a 79 91 href={pokemon.url} 80 92 target="_blank" 81 93 rel="noopener noreferrer" 82 - className="text-blue-600 underline" 94 + className="text-blue-600 underline" 83 95 > 84 96 View 85 97 </a> ··· 89 101 </TableBody> 90 102 </Table> 91 103 <div className="flex justify-center gap-4 mt-4"> 92 - <button 93 - type="button" 94 - onClick={() => setCurrentOffset(previousOffset ? parseInt(previousOffset) : undefined)} 104 + <Link 105 + to="/basic" 106 + className={buttonVariants({ 107 + variant: "outline", 108 + className: cn(!previousOffset && "opacity-50 cursor-not-allowed"), 109 + })} 110 + search={{ offset: Number(previousOffset) }} 95 111 disabled={!previousOffset} 96 - className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50 disabled:cursor-not-allowed" 97 112 > 98 113 Previous 99 - </button> 100 - <button 101 - type="button" 102 - onClick={() => setCurrentOffset(nextOffset ? parseInt(nextOffset) : undefined)} 114 + </Link> 115 + <Link 116 + to="/basic" 117 + search={{ offset: Number(nextOffset) }} 118 + className={buttonVariants({ 119 + variant: "outline", 120 + className: cn(!nextOffset && "opacity-50 cursor-not-allowed"), 121 + })} 103 122 disabled={!nextOffset} 104 - className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50 disabled:cursor-not-allowed" 105 123 > 106 124 Next 107 - </button> 125 + </Link> 108 126 </div> 109 127 </div> 110 128 );