├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── README.md ├── astro.config.mjs ├── package.json ├── pnpm-lock.yaml ├── public ├── 68747470733a2f2f6e616e6f73746f7265732e6769746875622e696f2f6e616e6f73746f7265732f6c6f676f2e737667.svg ├── corgi-pal.png ├── favicon.svg ├── pokeball.png ├── rubber-corgi.png └── tanstack.png ├── sandbox.config.json ├── src ├── api │ └── pokemon.ts ├── components │ ├── nanostores │ │ ├── ReactCounter.tsx │ │ ├── SolidCounter.tsx │ │ └── store.ts │ ├── pokedex-complete │ │ ├── ReactPicture.tsx │ │ ├── SolidStats.tsx │ │ ├── Svelte.svelte │ │ ├── SvelteDetails.svelte │ │ ├── VueEvolution.vue │ │ ├── ignore │ │ │ ├── Background.tsx │ │ │ └── PokedexHeader.tsx │ │ └── store.ts │ ├── pokedex │ │ ├── ReactPicture.tsx │ │ ├── SolidStats.tsx │ │ ├── Svelte.svelte │ │ ├── SvelteDetails.svelte │ │ ├── VueEvolution.vue │ │ ├── ignore │ │ │ ├── Background.tsx │ │ │ └── PokedexHeader.tsx │ │ └── store.ts │ └── ssr │ │ ├── Devtools.tsx │ │ ├── SSRName.tsx │ │ ├── SSRStats.tsx │ │ └── store.ts ├── env.d.ts ├── pages │ ├── index.astro │ ├── nanostores.astro │ ├── pokedex-complete.astro │ ├── pokedex.astro │ └── ssr.astro ├── styles │ └── global.css └── utils │ └── fns.ts ├── tailwind.config.cjs └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kitchen Sink: Microfrontends with Astro 2 | 3 | ## Install 4 | 5 | ```shell 6 | pnpm install 7 | ``` 8 | 9 | ## Dev Mode 10 | 11 | ```shell 12 | pnpm dev 13 | ``` 14 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import preact from '@astrojs/preact'; 3 | import react from '@astrojs/react'; 4 | import svelte from '@astrojs/svelte'; 5 | import vue from '@astrojs/vue'; 6 | import solid from '@astrojs/solid-js'; 7 | 8 | // https://astro.build/config 9 | import tailwind from "@astrojs/tailwind"; 10 | 11 | // https://astro.build/config 12 | export default defineConfig({ 13 | // Enable many frameworks to support all different kinds of components. 14 | integrations: [preact(), react(), svelte(), vue(), solid(), tailwind()] 15 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/framework-multiple", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/preact": "^2.0.3", 15 | "@astrojs/react": "^2.0.2", 16 | "@astrojs/solid-js": "^2.0.2", 17 | "@astrojs/svelte": "^2.0.2", 18 | "@astrojs/tailwind": "^3.0.1", 19 | "@astrojs/vue": "^2.0.1", 20 | "@nanostores/react": "^0.4.1", 21 | "@nanostores/solid": "^0.3.2", 22 | "@nanostores/vue": "^0.6.1", 23 | "@tanstack/query-core": "5.0.0-alpha.0", 24 | "@tanstack/react-query": "5.0.0-alpha.0", 25 | "@tanstack/react-query-devtools": "5.0.0-alpha.0", 26 | "@tanstack/solid-query": "5.0.0-alpha.0", 27 | "@tanstack/svelte-query": "5.0.0-alpha.0", 28 | "@tanstack/vue-query": "5.0.0-alpha.0", 29 | "astro": "^2.0.4", 30 | "colorthief": "^2.4.0", 31 | "nanostores": "^0.7.1", 32 | "pokeapi-typescript": "^2.1.0", 33 | "preact": "^10.7.3", 34 | "react": "^18.1.0", 35 | "react-dom": "^18.1.0", 36 | "solid-js": "^1.4.3", 37 | "svelte": "^3.48.0", 38 | "svelte-previous": "^2.1.3", 39 | "tailwindcss": "^3.0.24", 40 | "vue": "^3.2.37" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/68747470733a2f2f6e616e6f73746f7265732e6769746875622e696f2f6e616e6f73746f7265732f6c6f676f2e737667.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/corgi-pal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardeora/astro-tanstack-lwj/42b606641a31b11d63709713884c9a20a1b1cc62/public/corgi-pal.png -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /public/pokeball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardeora/astro-tanstack-lwj/42b606641a31b11d63709713884c9a20a1b1cc62/public/pokeball.png -------------------------------------------------------------------------------- /public/rubber-corgi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardeora/astro-tanstack-lwj/42b606641a31b11d63709713884c9a20a1b1cc62/public/rubber-corgi.png -------------------------------------------------------------------------------- /public/tanstack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ardeora/astro-tanstack-lwj/42b606641a31b11d63709713884c9a20a1b1cc62/public/tanstack.png -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": false, 4 | "view": "browser", 5 | "template": "node", 6 | "container": { 7 | "port": 3000, 8 | "startScript": "start", 9 | "node": "14" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/api/pokemon.ts: -------------------------------------------------------------------------------- 1 | import type { IEvolutionChain, IPokemon } from "pokeapi-typescript"; 2 | import PokeAPI from "pokeapi-typescript"; 3 | import ColorThief from "colorthief"; 4 | import { properCase } from "../utils/fns"; 5 | 6 | const API_URL = "https://pokeapi.aryandeora.com/api/pokeapi"; 7 | export interface Pokemon extends Omit { 8 | sprites: { 9 | other: { 10 | "official-artwork": { 11 | front_default: string; 12 | }; 13 | }; 14 | }; 15 | } 16 | 17 | export const getPokemon = async (id: number) => { 18 | const pokemon = await fetch(`${API_URL}?method=get_pokemon&id=${id}`).then( 19 | (res) => res.json() 20 | ); 21 | return pokemon as Pokemon; 22 | }; 23 | 24 | interface IChainItem { 25 | id: number; 26 | name: string; 27 | } 28 | 29 | export const getEvolutionChain = async (id: number) => { 30 | const chainItems = await fetch( 31 | `${API_URL}?method=get_evolution_chain&id=${id}` 32 | ).then((res) => res.json()); 33 | return chainItems as IChainItem[]; 34 | }; 35 | 36 | export const getTypesAndWeaknesses = async (id: number) => { 37 | const types = await fetch( 38 | `${API_URL}?method=get_types_and_weaknesses&id=${id}` 39 | ).then((res) => res.json()); 40 | return types as { 41 | types: string[]; 42 | weaknesses: string[]; 43 | }; 44 | }; 45 | 46 | export const getStats = async (id: number) => { 47 | const stats = await fetch(`${API_URL}?method=get_stats&id=${id}`).then( 48 | (res) => res.json() 49 | ); 50 | return stats as { 51 | stats: { 52 | name: string; 53 | value: number; 54 | }[]; 55 | }; 56 | }; 57 | 58 | export async function getColorPalette( 59 | id: number 60 | ): Promise<[number, number, number]> { 61 | const palette = await fetch( 62 | `${API_URL}?method=get_color_palette&id=${id}` 63 | ).then((res) => res.json()); 64 | return palette; 65 | } 66 | -------------------------------------------------------------------------------- /src/components/nanostores/ReactCounter.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useMemo, ReactNode } from "react"; 2 | import { getPokemon } from "../../api/pokemon"; 3 | import { useQuery } from "@tanstack/react-query"; 4 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 5 | import { useStore } from "@nanostores/react"; 6 | import { counter, client } from "./store"; 7 | 8 | /** A counter written with React */ 9 | export function ReactCounter() { 10 | const [count, setCount] = useState(0); 11 | 12 | const add = () => setCount(count + 1); 13 | const subtract = () => setCount(count - 1); 14 | 15 | return ( 16 |
17 |
18 | 19 |
20 | {count} 21 |
22 | 23 |
24 | 30 | 31 | 37 |
38 | 39 | {/*
{JSON.stringify(pokemonQuery.data?.name, null, 2)}
*/} 40 | 41 | 42 | 43 | {/* */} 44 |
45 | ); 46 | } 47 | 48 | const usePokemon = (count: number) => { 49 | return useQuery( 50 | { 51 | queryKey: ["pokemon", count], 52 | queryFn: () => getPokemon(count), 53 | }, 54 | client 55 | ); 56 | }; 57 | 58 | function CorgiList({ count }: { count: number }) { 59 | return ( 60 |
61 | {Array.from({ length: count }, (_, i) => ( 62 |
63 | Corgi 68 |
69 | ))} 70 |
71 | ); 72 | } 73 | 74 | function Header() { 75 | return ( 76 |
77 |
React Component
78 |
79 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 |
94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /src/components/nanostores/SolidCounter.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | import { Accessor, createMemo, createSignal, Index } from "solid-js"; 4 | import { getPokemon } from "../../api/pokemon"; 5 | import { client, counter } from "./store"; 6 | import { createQuery } from "@tanstack/solid-query"; 7 | import { useStore } from "@nanostores/solid"; 8 | 9 | /** A counter written with Solid */ 10 | export function SolidCounter() { 11 | const [count, setCount] = createSignal(0); 12 | const add = () => setCount(count() + 1); 13 | const subtract = () => setCount(count() - 1); 14 | 15 | return ( 16 |
17 |
18 | 19 |
20 | {count()} 21 |
22 | 23 |
24 | 30 | 31 | 37 |
38 | 39 | {/*
{JSON.stringify(pokemonQuery.data?.name, null, 2)}
*/} 40 | 41 |
42 | ); 43 | } 44 | 45 | const usePokemon = (count: Accessor) => { 46 | return createQuery( 47 | () => ({ 48 | queryKey: ["pokemon", count()], 49 | queryFn: () => getPokemon(count()), 50 | }), 51 | () => client 52 | ); 53 | }; 54 | 55 | function CorgiList(props: { count: number }) { 56 | const countArray = createMemo(() => 57 | Array.from({ length: props.count }, (_, i) => i) 58 | ); 59 | 60 | return ( 61 |
62 | 63 | {(i) => ( 64 |
65 | Corgi 70 |
71 | )} 72 |
73 |
74 | ); 75 | } 76 | 77 | function Header() { 78 | return ( 79 |
80 |
Solid Component
81 |
82 | 88 | 89 | 97 | 98 | 99 | 100 | 101 | 109 | 110 | 111 | 112 | 113 | 121 | 122 | 123 | 124 | 125 | 133 | 134 | 135 | 136 | 137 | 138 | 142 | 147 | 151 | 156 | 160 | 164 | 165 |
166 |
167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /src/components/nanostores/store.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/query-core"; 2 | import { atom } from "nanostores"; 3 | 4 | export const counter = atom(1); 5 | export const client = new QueryClient(); 6 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/ReactPicture.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useMemo, ReactNode } from "react"; 2 | import { getPokemon, getColorPalette } from "../../api/pokemon"; 3 | import { client, counter } from "./store"; 4 | import { useQuery } from "@tanstack/react-query"; 5 | import { useStore } from "@nanostores/react"; 6 | import { createLeadingZero } from "../../utils/fns"; 7 | 8 | /** A counter written with React */ 9 | export function ReactPicture() { 10 | const count = useStore(counter); 11 | const add = () => counter.set(counter.get() + 1); 12 | const subtract = () => counter.set(counter.get() - 1); 13 | 14 | const { data } = useQuery( 15 | { 16 | queryKey: ["pokemon", count], 17 | queryFn: () => getPokemon(count), 18 | placeholderData: (prev) => prev, 19 | }, 20 | client 21 | ); 22 | 23 | return ( 24 |
25 |
26 |

Picture

27 |
28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | 44 |
45 | {data && ( 46 | {data.name} 51 | )} 52 |
53 | 54 |
55 | 75 |
76 | #{createLeadingZero(count)} 77 |
78 | 98 |
99 |
100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/SolidStats.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | import { createSignal, For, Index } from "solid-js"; 4 | import { getPokemon, getColorPalette, getStats } from "../../api/pokemon"; 5 | import { client, counter } from "./store"; 6 | import { createQuery } from "@tanstack/solid-query"; 7 | import { useStore } from "@nanostores/solid"; 8 | import { createLeadingZero } from "../../utils/fns"; 9 | 10 | /** A counter written with Solid */ 11 | export function SolidStats() { 12 | const count = useStore(counter); 13 | const add = () => counter.set(counter.get() + 1); 14 | const subtract = () => counter.set(counter.get() - 1); 15 | 16 | const statsQuery = createQuery( 17 | () => ({ 18 | queryKey: ["pokemon", count()], 19 | queryFn: () => getPokemon(count()), 20 | placeholderData: (prev) => prev, 21 | select(data) { 22 | return data.stats.map((stat) => ({ 23 | label: stat.stat.name, 24 | value: stat.base_stat, 25 | })); 26 | }, 27 | }), 28 | () => client 29 | ); 30 | 31 | const colorsQuery = createQuery( 32 | () => ({ 33 | queryKey: ["color", count()], 34 | queryFn: () => getColorPalette(count()), 35 | placeholderData: (prev) => prev, 36 | }), 37 | () => client 38 | ); 39 | 40 | const getFillStatColor = (color: [number, number, number]) => { 41 | return `hsl(${color[0]}, 50%, 62%)`; 42 | }; 43 | 44 | const getBarColor = (color: [number, number, number]) => { 45 | return `hsl(${color[0]}, 10%, 32%)`; 46 | }; 47 | 48 | const getTextColor = (color: [number, number, number]) => { 49 | return `hsl(${color[0]}, 20%, 32%)`; 50 | }; 51 | 52 | return ( 53 |
54 |
55 |

Stats

56 |
57 | 63 | 64 | 72 | 73 | 74 | 75 | 76 | 84 | 85 | 86 | 87 | 88 | 96 | 97 | 98 | 99 | 100 | 108 | 109 | 110 | 111 | 112 | 113 | 117 | 122 | 126 | 131 | 135 | 139 | 140 |
141 |
142 | 143 |
144 |
145 |
146 | 147 | {(stat, i) => ( 148 |
149 |
157 | {stat().label} 158 |
159 |
160 | )} 161 |
162 |
163 |
164 | 165 | {(stat, i) => ( 166 |
167 |
175 |
184 |
185 |
186 | )} 187 |
188 |
189 |
190 | 191 | {(stat, i) => ( 192 |
193 |
201 | {Math.round((stat().value / 100) * 150)} 202 |
203 |
204 | )} 205 |
206 |
207 |
208 |
209 | 210 |
211 | 231 |
232 | #{createLeadingZero(count())} 233 |
234 | 254 |
255 |
256 | ); 257 | } 258 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/Svelte.svelte: -------------------------------------------------------------------------------- 1 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/SvelteDetails.svelte: -------------------------------------------------------------------------------- 1 | 4 | 34 | 35 |
36 |
37 |

Details

38 |
39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 |

Type

52 |
53 | { #if $query.data } 54 | 55 | {#each $query.data.types as type} 56 | 57 | {type} 58 | 59 | {/each } 60 | 61 | {/if} 62 |
63 | 64 |

Weaknesses

65 | { #if $query.data } 66 |
67 | {#each $query.data.weaknesses as weakness} 68 | 69 | {weakness} 70 | 71 | {/each } 72 |
73 | {/if} 74 |
75 | 76 |
77 | 86 |
87 | #{createLeadingZero($count)} 88 |
89 | 98 |
99 |
100 | 101 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/VueEvolution.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 155 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/ignore/Background.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useMemo, ReactNode } from "react"; 2 | import { getPokemon, getColorPalette } from "../../../api/pokemon"; 3 | import { client, counter } from "../store"; 4 | import { QueryClientProvider, useQuery } from "@tanstack/react-query"; 5 | import { useStore } from "@nanostores/react"; 6 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 7 | 8 | /** A counter written with React */ 9 | export function Background() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | const constrainValue = (value, min, max) => { 19 | return Math.min(Math.max(value, min), max); 20 | }; 21 | 22 | function BackgroundColor() { 23 | const count = useStore(counter); 24 | 25 | const { data } = useQuery( 26 | { 27 | queryKey: ["color", count], 28 | queryFn: () => getColorPalette(count), 29 | placeholderData: (prev) => prev, 30 | }, 31 | client 32 | ); 33 | 34 | const color = !data 35 | ? "" 36 | : `linear-gradient( 37 | 45deg, 38 | hsl(${constrainValue(data[0] + 25, 0, 360)}deg ${constrainValue( 39 | data[1] + 50, 40 | 0, 41 | 100 42 | )}% ${constrainValue(data[2] - 10, 0, 100)}%) 0%, 43 | hsl(${constrainValue(data[0] - 25, 0, 360)}deg ${constrainValue( 44 | data[1] + 25, 45 | 0, 46 | 100 47 | )}% ${constrainValue(data[2] + 10, 0, 100)}%) 100% 48 | )`; 49 | 50 | const iconColor = !data 51 | ? "" 52 | : `hsl(${data[0]}deg ${constrainValue( 53 | data[1] + 25, 54 | 0, 55 | 100 56 | )}% ${constrainValue(data[2] - 10, 0, 100)}%)`; 57 | 58 | return ( 59 |
60 |
73 |
84 | 85 |
86 |
87 | ); 88 | } 89 | 90 | function Icon({ color }: { color: string }) { 91 | const size = "75vh"; 92 | return ( 93 | 107 | 108 | 113 | 114 | 115 | 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/ignore/PokedexHeader.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | import { createSignal } from "solid-js"; 4 | import { getPokemon, getColorPalette } from "../../../api/pokemon"; 5 | import { client, counter } from "../store"; 6 | import { createQuery } from "@tanstack/solid-query"; 7 | import { useStore } from "@nanostores/solid"; 8 | import { createLeadingZero } from "../../../utils/fns"; 9 | 10 | /** A counter written with Solid */ 11 | export function PokedexHeader() { 12 | const count = useStore(counter); 13 | 14 | const pokemonQuery = createQuery( 15 | () => ({ 16 | queryKey: ["pokemon", count()], 17 | queryFn: () => getPokemon(count()), 18 | placeholderData: (prev) => prev, 19 | }), 20 | () => client 21 | ); 22 | 23 | return ( 24 | <> 25 |
26 |

27 | Pokémon 28 |

29 |

30 | #{createLeadingZero(count())} 31 |

32 |
33 | 34 |
35 |

36 | {pokemonQuery.data?.name} 37 |

38 |
39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/pokedex-complete/store.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/query-core"; 2 | import { atom } from "nanostores"; 3 | 4 | export const counter = atom(1); 5 | export const client = new QueryClient(); 6 | -------------------------------------------------------------------------------- /src/components/pokedex/ReactPicture.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useMemo, ReactNode } from "react"; 2 | import { getPokemon, getColorPalette, Pokemon } from "../../api/pokemon"; 3 | import { client, counter } from "./store"; 4 | import { useQuery } from "@tanstack/react-query"; 5 | import { useStore } from "@nanostores/react"; 6 | import { createLeadingZero } from "../../utils/fns"; 7 | 8 | /** A counter written with React */ 9 | export function ReactPicture() { 10 | const count = useStore(counter); 11 | const add = () => counter.set(counter.get() + 1); 12 | const subtract = () => counter.set(counter.get() - 1); 13 | 14 | const pokemonQuery = useQuery( 15 | { 16 | queryKey: ["pokemon", count], 17 | queryFn: () => getPokemon(count), 18 | }, 19 | client 20 | ); 21 | 22 | return ( 23 |
24 |
25 | 26 | 27 | 28 | 29 |
30 | ); 31 | } 32 | 33 | function Header() { 34 | return ( 35 |
36 |

Picture

37 |
38 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 | ); 54 | } 55 | 56 | function Buttons(props: { 57 | add: () => void; 58 | subtract: () => void; 59 | count: number; 60 | }) { 61 | return ( 62 |
63 | 83 |
84 | #{createLeadingZero(props.count)} 85 |
86 | 106 |
107 | ); 108 | } 109 | 110 | function PokemonImage(props: { data?: Pokemon }) { 111 | const { data } = props; 112 | return ( 113 |
114 | {data && ( 115 | {data.name} 120 | )} 121 |
122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /src/components/pokedex/SolidStats.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | import { createSignal, For, Index } from "solid-js"; 4 | import { getPokemon, getColorPalette, getStats } from "../../api/pokemon"; 5 | import { client, counter } from "./store"; 6 | import { createQuery } from "@tanstack/solid-query"; 7 | import { useStore } from "@nanostores/solid"; 8 | import { createLeadingZero } from "../../utils/fns"; 9 | 10 | /** A counter written with Solid */ 11 | export function SolidStats() { 12 | const count = useStore(counter); 13 | const add = () => counter.set(counter.get() + 1); 14 | const subtract = () => counter.set(counter.get() - 1); 15 | 16 | const statsQuery = createQuery( 17 | () => ({ 18 | queryKey: ["pokemon", count()], 19 | queryFn: () => getPokemon(count()), 20 | placeholderData: (prev) => prev, 21 | select: (data) => { 22 | return data.stats.map((stat) => ({ 23 | label: stat.stat.name, 24 | value: stat.base_stat, 25 | })); 26 | }, 27 | }), 28 | () => client 29 | ); 30 | 31 | return ( 32 |
33 |
34 | 35 | 36 | 37 | 38 |
39 | ); 40 | } 41 | 42 | interface StatsGraphProps { 43 | data?: Array<{ 44 | // Stat Label 45 | label: string; 46 | // Stat Value 47 | value: number; 48 | }>; 49 | } 50 | 51 | function StatsGraph(props: StatsGraphProps) { 52 | const count = useStore(counter); 53 | 54 | const colorsQuery = createQuery( 55 | () => ({ 56 | queryKey: ["color", count()], 57 | queryFn: () => getColorPalette(count()), 58 | placeholderData: (prev) => prev, 59 | }), 60 | () => client 61 | ); 62 | 63 | const getFillStatColor = (color: [number, number, number]) => { 64 | return `hsl(${color[0]}, 50%, 62%)`; 65 | }; 66 | 67 | const getBarColor = (color: [number, number, number]) => { 68 | return `hsl(${color[0]}, 10%, 32%)`; 69 | }; 70 | 71 | const getTextColor = (color: [number, number, number]) => { 72 | return `hsl(${color[0]}, 20%, 32%)`; 73 | }; 74 | 75 | return ( 76 |
77 |
78 |
79 | 80 | {(stat, i) => ( 81 |
82 |
90 | {stat().label} 91 |
92 |
93 | )} 94 |
95 |
96 |
97 | 98 | {(stat, i) => ( 99 |
100 |
108 |
117 |
118 |
119 | )} 120 |
121 |
122 |
123 | 124 | {(stat, i) => ( 125 |
126 |
134 | {Math.round((stat().value / 100) * 150)} 135 |
136 |
137 | )} 138 |
139 |
140 |
141 |
142 | ); 143 | } 144 | 145 | function Header() { 146 | return ( 147 |
148 |

Stats

149 |
150 | 156 | 157 | 165 | 166 | 167 | 168 | 169 | 177 | 178 | 179 | 180 | 181 | 189 | 190 | 191 | 192 | 193 | 201 | 202 | 203 | 204 | 205 | 206 | 210 | 215 | 219 | 224 | 228 | 232 | 233 |
234 |
235 | ); 236 | } 237 | 238 | function Buttons(props: { 239 | add: () => void; 240 | subtract: () => void; 241 | count: number; 242 | }) { 243 | return ( 244 |
245 | 265 |
266 | #{createLeadingZero(props.count)} 267 |
268 | 288 |
289 | ); 290 | } 291 | -------------------------------------------------------------------------------- /src/components/pokedex/Svelte.svelte: -------------------------------------------------------------------------------- 1 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/pokedex/SvelteDetails.svelte: -------------------------------------------------------------------------------- 1 | 4 | 34 | 35 |
36 |
37 |

Details

38 |
39 | 40 | 44 | 45 | 46 | 47 |
48 |
49 | 50 |
51 |

Type

52 |
53 | { #if $query.data } 54 | 55 | {#each $query.data.types as type} 56 | 57 | {type} 58 | 59 | {/each } 60 | 61 | {/if} 62 |
63 | 64 |

Weaknesses

65 | { #if $query.data } 66 |
67 | {#each $query.data.weaknesses as weakness} 68 | 69 | {weakness} 70 | 71 | {/each } 72 |
73 | {/if} 74 |
75 | 76 |
77 | 86 |
87 | #{createLeadingZero($count)} 88 |
89 | 98 |
99 |
100 | 101 | -------------------------------------------------------------------------------- /src/components/pokedex/VueEvolution.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 155 | -------------------------------------------------------------------------------- /src/components/pokedex/ignore/Background.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useMemo, ReactNode } from "react"; 2 | import { getPokemon, getColorPalette } from "../../../api/pokemon"; 3 | import { client, counter } from "../store"; 4 | import { QueryClientProvider, useQuery } from "@tanstack/react-query"; 5 | import { useStore } from "@nanostores/react"; 6 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 7 | 8 | /** A counter written with React */ 9 | export function Background() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | const constrainValue = (value, min, max) => { 19 | return Math.min(Math.max(value, min), max); 20 | }; 21 | 22 | function BackgroundColor() { 23 | const count = useStore(counter); 24 | 25 | const { data } = useQuery( 26 | { 27 | queryKey: ["color", count], 28 | queryFn: () => getColorPalette(count), 29 | placeholderData: (prev) => prev, 30 | }, 31 | client 32 | ); 33 | 34 | const color = !data 35 | ? "" 36 | : `linear-gradient( 37 | 45deg, 38 | hsl(${constrainValue(data[0] + 25, 0, 360)}deg ${constrainValue( 39 | data[1] + 50, 40 | 0, 41 | 100 42 | )}% ${constrainValue(data[2] - 10, 0, 100)}%) 0%, 43 | hsl(${constrainValue(data[0] - 25, 0, 360)}deg ${constrainValue( 44 | data[1] + 25, 45 | 0, 46 | 100 47 | )}% ${constrainValue(data[2] + 10, 0, 100)}%) 100% 48 | )`; 49 | 50 | const iconColor = !data 51 | ? "" 52 | : `hsl(${data[0]}deg ${constrainValue( 53 | data[1] + 25, 54 | 0, 55 | 100 56 | )}% ${constrainValue(data[2] - 10, 0, 100)}%)`; 57 | 58 | return ( 59 |
60 |
73 |
84 | 85 |
86 |
87 | ); 88 | } 89 | 90 | function Icon({ color }: { color: string }) { 91 | const size = "75vh"; 92 | return ( 93 | 107 | 108 | 113 | 114 | 115 | 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/components/pokedex/ignore/PokedexHeader.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | import { createSignal } from "solid-js"; 4 | import { getPokemon, getColorPalette } from "../../../api/pokemon"; 5 | import { client, counter } from "../store"; 6 | import { createQuery } from "@tanstack/solid-query"; 7 | import { useStore } from "@nanostores/solid"; 8 | import { createLeadingZero } from "../../../utils/fns"; 9 | 10 | /** A counter written with Solid */ 11 | export function PokedexHeader() { 12 | const count = useStore(counter); 13 | 14 | const pokemonQuery = createQuery( 15 | () => ({ 16 | queryKey: ["pokemon", count()], 17 | queryFn: () => getPokemon(count()), 18 | placeholderData: (prev) => prev, 19 | }), 20 | () => client 21 | ); 22 | 23 | return ( 24 | <> 25 |
26 |

27 | Pokémon 28 |

29 |

30 | #{createLeadingZero(count())} 31 |

32 |
33 | 34 |
35 |

36 | {pokemonQuery.data?.name} 37 |

38 |
39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/pokedex/store.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/query-core"; 2 | import { atom } from "nanostores"; 3 | 4 | export const counter = atom(1); 5 | export const client = new QueryClient(); 6 | -------------------------------------------------------------------------------- /src/components/ssr/Devtools.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useMemo, ReactNode } from "react"; 2 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 3 | import { client } from "./store"; 4 | 5 | /** A counter written with React */ 6 | export function Devtools() { 7 | return ; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/ssr/SSRName.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | import { createSignal, For, Index } from "solid-js"; 4 | import { getPokemon, Pokemon } from "../../api/pokemon"; 5 | import { client, counter } from "./store"; 6 | import { createQuery } from "@tanstack/solid-query"; 7 | import { useStore } from "@nanostores/solid"; 8 | 9 | /** A counter written with Solid */ 10 | export function SSRName(props: { pokemon: Pokemon }) { 11 | const count = useStore(counter); 12 | 13 | // const pokeQuery = createQuery( 14 | // () => ({ 15 | // queryKey: ["pokemon", count()], 16 | // queryFn: () => getPokemon(count()), 17 | // placeholderData: (prev) => prev, 18 | // initialData: props.pokemon, 19 | // staleTime: 1000 * 20, 20 | // }), 21 | // () => client 22 | // ); 23 | 24 | return ( 25 |

26 | {/* {pokeQuery.data.name} */} 27 |

28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/ssr/SSRStats.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | import { createSignal, For, Index } from "solid-js"; 4 | import { 5 | getPokemon, 6 | getColorPalette, 7 | getStats, 8 | Pokemon, 9 | } from "../../api/pokemon"; 10 | import { client, counter } from "./store"; 11 | import { createQuery } from "@tanstack/solid-query"; 12 | import { useStore } from "@nanostores/solid"; 13 | import { createLeadingZero } from "../../utils/fns"; 14 | 15 | /** A counter written with Solid */ 16 | export function SSRStats(props: { 17 | stats: { 18 | value: number; 19 | label: string; 20 | }[]; 21 | }) { 22 | const count = useStore(counter); 23 | const add = () => counter.set(counter.get() + 1); 24 | const subtract = () => counter.set(counter.get() - 1); 25 | 26 | const statsQuery = createQuery( 27 | () => ({ 28 | queryKey: ["pokemon-stats", count()], 29 | queryFn: () => getStats(count()), 30 | placeholderData: (prev) => prev, 31 | initialData: () => (count() === 1 ? props.stats : undefined), 32 | staleTime: 1000 * 60, 33 | }), 34 | () => client 35 | ); 36 | 37 | return ( 38 |
39 |
40 |

Stats

41 |
42 | 48 | 49 | 57 | 58 | 59 | 60 | 61 | 69 | 70 | 71 | 72 | 73 | 81 | 82 | 83 | 84 | 85 | 93 | 94 | 95 | 96 | 97 | 98 | 102 | 107 | 111 | 116 | 120 | 124 | 125 |
126 |
127 | 128 |
129 |
130 |
131 | 132 | {(stat, i) => ( 133 |
134 |
{stat().label}
135 |
136 | )} 137 |
138 |
139 |
140 | 141 | {(stat, i) => ( 142 |
143 |
144 |
150 |
151 |
152 | )} 153 |
154 |
155 |
156 | 157 | {(stat, i) => ( 158 |
159 |
160 | {Math.round((stat().value / 100) * 150)} 161 |
162 |
163 | )} 164 |
165 |
166 |
167 |
168 | 169 |
170 | 190 |
191 | #{createLeadingZero(count())} 192 |
193 | 213 |
214 |
215 | ); 216 | } 217 | -------------------------------------------------------------------------------- /src/components/ssr/store.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/query-core"; 2 | import { atom } from "nanostores"; 3 | 4 | export const counter = atom(1); 5 | export const client = new QueryClient(); 6 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // Style Imports 3 | import '../styles/global.css'; 4 | 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

Learn With Jason

20 |

Sharing state between multiple UI libraries with Astro

21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | + 42 |
43 | TanStack Logo 44 |
45 | + 46 |
47 | 48 |
49 |
50 |
51 | 52 | 143 |
144 | 145 | 146 | -------------------------------------------------------------------------------- /src/pages/nanostores.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // Style Imports 3 | import { ReactCounter } from '../components/nanostores/ReactCounter'; 4 | import { SolidCounter } from '../components/nanostores/SolidCounter'; 5 | import '../styles/global.css'; 6 | 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 40 |
41 |

Nanostores + Query

42 |
43 | 44 | 45 |
46 | 47 | 48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /src/pages/pokedex-complete.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // Style Imports 3 | import '../styles/global.css'; 4 | import { getColorPalette, getEvolutionChain, getPokemon, getStats, getTypesAndWeaknesses } from '../api/pokemon'; 5 | import { Background } from '../components/pokedex-complete/ignore/Background'; 6 | import { PokedexHeader } from '../components/pokedex-complete/ignore/PokedexHeader'; 7 | 8 | 9 | import { ReactPicture } from '../components/pokedex-complete/ReactPicture'; 10 | import { SolidStats } from '../components/pokedex-complete/SolidStats'; 11 | import Svelte from '../components/pokedex-complete/Svelte.svelte'; 12 | import VueEvolution from '../components/pokedex-complete/VueEvolution.vue'; 13 | --- 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Pokedex 23 | 24 | 25 |
26 | 27 |

Astro + TanStack Query

28 |

Pokédex Example

29 |
30 | 31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 | 41 |
42 | 43 | 44 | 45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /src/pages/pokedex.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // Style Imports 3 | import '../styles/global.css'; 4 | import { getColorPalette, getEvolutionChain, getPokemon, getStats, getTypesAndWeaknesses } from '../api/pokemon'; 5 | import { Background } from '../components/pokedex/ignore/Background'; 6 | import { PokedexHeader } from '../components/pokedex/ignore/PokedexHeader'; 7 | 8 | 9 | import { ReactPicture } from '../components/pokedex/ReactPicture'; 10 | import { SolidStats } from '../components/pokedex/SolidStats'; 11 | import Svelte from '../components/pokedex/Svelte.svelte'; 12 | import VueEvolution from '../components/pokedex/VueEvolution.vue'; 13 | --- 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 |

Astro + TanStack Query

27 |

Pokédex Example

28 |
29 | 30 | 31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | 40 |
41 | 42 | 43 | 44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /src/pages/ssr.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // Style Imports 3 | 4 | import { getPokemon, getStats } from '../api/pokemon'; 5 | import { Devtools } from '../components/ssr/Devtools'; 6 | import { SSRName } from '../components/ssr/SSRName'; 7 | import { SSRStats } from '../components/ssr/SSRStats'; 8 | import '../styles/global.css'; 9 | 10 | const bulbasaurStats = await getStats(1); 11 | --- 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 45 |
46 |

TanStack Query SSR

47 |
48 | 49 | 50 |
51 | 52 | 53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .pokemon-grid { 6 | display: grid; 7 | grid-template-columns: repeat(2, minmax(200px, 1fr)); 8 | grid-gap: 1rem; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/fns.ts: -------------------------------------------------------------------------------- 1 | export const properCase = (str: string) => { 2 | return str 3 | .split(" ") 4 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 5 | .join(" "); 6 | }; 7 | 8 | export const createLeadingZero = (num: number) => { 9 | return num.toString().padStart(3, "0"); 10 | }; 11 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 4 | theme: { 5 | extend: { 6 | fontFamily: { 7 | fun: ["ruddy", "sans-serif"], 8 | quicksand: ["quicksand", "sans-serif"], 9 | }, 10 | }, 11 | }, 12 | plugins: [], 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base", 3 | "compilerOptions": { 4 | // Needed for TypeScript intellisense in the template inside Vue files 5 | "jsx": "preserve" 6 | } 7 | } 8 | --------------------------------------------------------------------------------