├── .eslintignore ├── .eslintrc.cjs ├── .github └── actions │ └── prepare │ └── action.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── README.md ├── astro.config.mjs ├── integrations ├── code-snippets.ts └── utils │ └── makeComponentNode.ts ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── fonts │ └── gordita │ │ ├── Gordita-Black-Italic.woff │ │ ├── Gordita-Black.woff │ │ ├── Gordita-Bold-Italic.woff │ │ ├── Gordita-Bold.eot │ │ ├── Gordita-Bold.ttf │ │ ├── Gordita-Bold.woff │ │ ├── Gordita-Light-Italic.woff │ │ ├── Gordita-Light.woff │ │ ├── Gordita-Medium-Italic.woff │ │ ├── Gordita-Medium.eot │ │ ├── Gordita-Medium.ttf │ │ ├── Gordita-Medium.woff │ │ ├── Gordita-Regular-Italic.woff │ │ ├── Gordita-Regular.eot │ │ ├── Gordita-Regular.ttf │ │ ├── Gordita-Regular.woff │ │ ├── Gordita-Thin-Italic.woff │ │ ├── Gordita-Thin.eot │ │ ├── Gordita-Thin.ttf │ │ ├── Gordita-Thin.woff │ │ ├── Gordita-Ultra-Italic.woff │ │ └── Gordita-Ultra.woff ├── global.css └── logos │ ├── solid-icon.svg │ └── solid-logo.svg ├── src ├── components │ ├── Aside.astro │ ├── CodeSample │ │ ├── CodeSample.astro │ │ ├── CodeSample.module.css │ │ ├── CodeSample.tsx │ │ ├── CodeSampleCompile.astro │ │ └── compileTS.ts │ ├── CodeSnippet │ │ └── CodeSnippet.astro │ ├── FrameworkAside.astro │ ├── FrameworkAside.tsx │ ├── Header │ │ ├── Header.astro │ │ ├── Logo.astro │ │ ├── MobileNavToggle.module.css │ │ ├── MobileNavToggle.tsx │ │ ├── ThemeToggle.module.css │ │ └── ThemeToggle.tsx │ ├── LeftSidebar.astro │ ├── Preferences │ │ ├── IfTS.astro │ │ ├── IfTS.tsx │ │ ├── Preferences.module.css │ │ ├── Preferences.tsx │ │ └── usePreferences.ts │ ├── RightSidebar.astro │ └── useTheme.ts ├── content.ts ├── content │ ├── config.ts │ └── docs │ │ ├── concepts │ │ └── tracking.mdx │ │ ├── start-here │ │ ├── index.mdx │ │ └── js-for-solid.mdx │ │ └── tutorial │ │ ├── _components │ │ └── FinishedBookshelf.tsx │ │ ├── index.mdx │ │ └── installing-solid.mdx ├── control-flow.d.ts ├── env.d.ts ├── layouts │ └── BaseLayout.astro ├── pages │ ├── [...slug].astro │ └── index.astro └── sidebar.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.cjs -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | project: true, 5 | tsconfigRootDir: __dirname, 6 | }, 7 | plugins: [ 8 | "@typescript-eslint", 9 | "astro", 10 | "jsx-a11y", 11 | "solid", 12 | "simple-import-sort", 13 | ], 14 | root: true, 15 | overrides: [ 16 | { 17 | // Define the configuration for `.astro` file. 18 | files: ["*.astro"], 19 | // Allows Astro components to be parsed. 20 | parser: "astro-eslint-parser", 21 | // Parse the script in `.astro` as TypeScript by adding the following configuration. 22 | // It's the setting you need when using TypeScript. 23 | parserOptions: { 24 | parser: "@typescript-eslint/parser", 25 | extraFileExtensions: [".astro"], 26 | }, 27 | rules: {}, 28 | }, 29 | { 30 | extends: ["plugin:solid/typescript"], 31 | files: ["*.tsx"], 32 | }, 33 | { 34 | files: ["*.tsx", "*.astro"], 35 | rules: { 36 | "solid/prefer-for": "off", 37 | }, 38 | }, 39 | ], 40 | extends: [ 41 | "eslint:recommended", 42 | "plugin:@typescript-eslint/recommended", 43 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 44 | "plugin:astro/recommended", 45 | "plugin:solid/typescript", 46 | ], 47 | rules: { 48 | "no-undef": "off", 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /.github/actions/prepare/action.yml: -------------------------------------------------------------------------------- 1 | description: Prepares the repo for a typical CI job 2 | 3 | name: Prepare 4 | runs: 5 | steps: 6 | - uses: pnpm/action-setup@v2 7 | - uses: actions/setup-node@v3 8 | with: 9 | cache: pnpm 10 | node-version: "18" 11 | - run: pnpm install --frozen-lockfile 12 | shell: bash 13 | using: composite 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.mdx -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": false 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode", "dbaeumer.vscode-eslint"], 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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sshhhh - we're working hard! Come back soon! 2 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import solidJs from "@astrojs/solid-js"; 3 | import Icons from "unplugin-icons/vite"; 4 | import mdx from "@astrojs/mdx"; 5 | import AutoImport from "astro-auto-import"; 6 | import { 7 | astroCodeSnippets, 8 | codeSnippetAutoImport, 9 | } from "./integrations/code-snippets"; 10 | 11 | export default defineConfig({ 12 | /* TODO: Update site property if we end up using another domain */ 13 | site: "https://docs.solidjs.com/", 14 | integrations: [ 15 | AutoImport({ 16 | imports: [codeSnippetAutoImport], 17 | }), 18 | solidJs(), 19 | astroCodeSnippets(), 20 | mdx(), 21 | ], 22 | vite: { 23 | plugins: [ 24 | Icons({ 25 | compiler: "solid", 26 | }), 27 | ], 28 | }, 29 | markdown: { 30 | shikiConfig: { 31 | theme: "github-dark", 32 | }, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /integrations/code-snippets.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Code forked from: 3 | https://github.com/withastro/docs/blob/main/integrations/astro-code-snippets.ts 4 | */ 5 | import type { AstroIntegration } from "astro"; 6 | import type { BlockContent, Parent, Root } from "mdast"; 7 | import type { Plugin, Transformer } from "unified"; 8 | import { visit } from "unist-util-visit"; 9 | import type { BuildVisitor } from "unist-util-visit/complex-types"; 10 | import { makeComponentNode } from "./utils/makeComponentNode"; 11 | 12 | const CodeSnippetTagname = "AutoImportedCodeSnippet"; 13 | export const codeSnippetAutoImport: Record = { 14 | "~/components/CodeSnippet/CodeSnippet.astro": [ 15 | ["default", CodeSnippetTagname], 16 | ], 17 | }; 18 | 19 | export interface CodeSnippetWrapper extends Parent { 20 | type: "codeSnippetWrapper"; 21 | children: BlockContent[]; 22 | } 23 | 24 | declare module "mdast" { 25 | interface BlockContentMap { 26 | codeSnippetWrapper: CodeSnippetWrapper; 27 | } 28 | } 29 | 30 | export function remarkCodeSnippets(): Plugin<[], Root> { 31 | const visitor: BuildVisitor = (code, index, parent) => { 32 | if (index === null || parent === null) return; 33 | 34 | const metaTitle = parseTitle(code.meta || ""); 35 | 36 | code.value = preprocessCode(code.value); 37 | 38 | const attributes = { 39 | title: encodeMarkdownStringProp(metaTitle), 40 | code: encodeMarkdownStringProp(code.value), 41 | }; 42 | 43 | const codeSnippetWrapper = makeComponentNode( 44 | CodeSnippetTagname, 45 | { attributes }, 46 | code 47 | ); 48 | 49 | parent.children.splice(index, 1, codeSnippetWrapper); 50 | }; 51 | 52 | const transformer: Transformer = (tree) => { 53 | visit(tree, "code", visitor); 54 | }; 55 | 56 | return function attacher() { 57 | return transformer; 58 | }; 59 | } 60 | 61 | function parseTitle(meta: string) { 62 | // Try to find the meta property `title="..."` or `title='...'`, 63 | // store its value and remove it from meta 64 | let title: string | undefined; 65 | meta = meta.replace( 66 | /(?:\s|^)title\s*=\s*(["'])(.*?)(? { 68 | title = content; 69 | return ""; 70 | } 71 | ); 72 | 73 | return title; 74 | } 75 | 76 | /** 77 | * Preprocesses the given raw code snippet before being handed to the syntax highlighter. 78 | * 79 | * Does the following things: 80 | * - Trims empty lines at the beginning or end of the code block 81 | * - Normalizes whitespace and line endings 82 | */ 83 | export function preprocessCode(code: string) { 84 | // Split the code into lines and remove any empty lines at the beginning & end 85 | const lines = code.split(/\r?\n/); 86 | 87 | while (lines.length > 0 && lines[0].trim().length === 0) { 88 | lines.shift(); 89 | } 90 | 91 | while (lines.length > 0 && lines[lines.length - 1].trim().length === 0) { 92 | lines.pop(); 93 | } 94 | 95 | // If only one line is left, trim any leading indentation 96 | if (lines.length === 1) lines[0] = lines[0].trimStart(); 97 | 98 | // Rebuild code with normalized line endings 99 | let preprocessedCode = lines.join("\n"); 100 | 101 | // Convert tabs to 2 spaces 102 | preprocessedCode = preprocessedCode.replace(/\t/g, " "); 103 | 104 | return preprocessedCode; 105 | } 106 | 107 | /** Encodes an optional string to allow passing it through Markdown/MDX component props */ 108 | export function encodeMarkdownStringProp(input: string | undefined) { 109 | return (input !== undefined && encodeURIComponent(input)) || undefined; 110 | } 111 | 112 | /** Encodes an optional string array to allow passing it through Markdown/MDX component props */ 113 | export function encodeMarkdownStringArrayProp(arrInput: string[] | undefined) { 114 | if (arrInput === undefined) return undefined; 115 | return ( 116 | arrInput.map((input) => encodeURIComponent(input)).join(",") || undefined 117 | ); 118 | } 119 | 120 | export function astroCodeSnippets(): AstroIntegration { 121 | return { 122 | name: "@astrojs/code-snippets", 123 | hooks: { 124 | "astro:config:setup": ({ updateConfig }) => { 125 | updateConfig({ 126 | markdown: { 127 | remarkPlugins: [remarkCodeSnippets()], 128 | }, 129 | }); 130 | }, 131 | }, 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /integrations/utils/makeComponentNode.ts: -------------------------------------------------------------------------------- 1 | import type { BlockContent } from "mdast"; 2 | import type { MdxJsxAttribute, MdxJsxFlowElement } from "mdast-util-mdx-jsx"; 3 | 4 | interface NodeProps { 5 | attributes?: Record; 6 | } 7 | 8 | /** 9 | * Create AST node for a custom component injection. 10 | * 11 | * @example 12 | * makeComponentNode('MyComponent', { prop: 'val' }, h('p', 'Paragraph inside component')) 13 | * 14 | */ 15 | export function makeComponentNode( 16 | name: string, 17 | { attributes = {} }: NodeProps = {}, 18 | ...children: BlockContent[] 19 | ): MdxJsxFlowElement { 20 | return { 21 | type: "mdxJsxFlowElement", 22 | name, 23 | attributes: Object.entries(attributes) 24 | // Filter out non-truthy attributes to avoid empty attrs being parsed as `true`. 25 | .filter(([_k, v]) => v !== false && Boolean(v)) 26 | .map(([name, value]) => ({ 27 | type: "mdxJsxAttribute", 28 | name, 29 | value: value as MdxJsxAttribute["value"], 30 | })), 31 | children, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "new-solid-docs", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview", 10 | "astro": "astro", 11 | "lint": "eslint . --max-warnings 0 --report-unused-disable-directives" 12 | }, 13 | "dependencies": { 14 | "@astrojs/mdx": "^0.18.3", 15 | "@astrojs/solid-js": "^2.1.0", 16 | "@kobalte/core": "^0.8.2", 17 | "@solid-primitives/mutation-observer": "^1.1.10", 18 | "@solid-primitives/storage": "^1.3.9", 19 | "@types/eslint": "^8.37.0", 20 | "@types/node": "^18.16.0", 21 | "@types/prettier": "^2.7.2", 22 | "astro": "^2.2.1", 23 | "solid-js": "^1.7.3", 24 | "unplugin-icons": "^0.16.1" 25 | }, 26 | "devDependencies": { 27 | "@iconify/json": "^2.2.47", 28 | "@types/mdast": "^3.0.10", 29 | "@typescript-eslint/eslint-plugin": "^5.57.1", 30 | "@typescript-eslint/parser": "^5.57.1", 31 | "astro-auto-import": "^0.2.1", 32 | "eslint": "^8.38.0", 33 | "eslint-plugin-astro": "^0.26.1", 34 | "eslint-plugin-jsx-a11y": "^6.7.1", 35 | "eslint-plugin-simple-import-sort": "^10.0.0", 36 | "eslint-plugin-solid": "^0.12.0", 37 | "mdast-util-mdx-jsx": "^2.1.2", 38 | "prettier": "^2.8.7", 39 | "prettier-plugin-astro": "^0.8.0", 40 | "typescript": "^5.0.4", 41 | "unified": "^10.1.2", 42 | "unist-util-visit": "^4.1.0" 43 | }, 44 | "packageManager": "pnpm@8.1.0" 45 | } 46 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Black-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Black-Italic.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Black.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Bold-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Bold-Italic.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Bold.eot -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Bold.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Light-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Light-Italic.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Light.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Medium-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Medium-Italic.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Medium.eot -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Medium.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Regular-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Regular-Italic.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Regular.eot -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Regular.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Thin-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Thin-Italic.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Thin.eot -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Thin.ttf -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Thin.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Ultra-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Ultra-Italic.woff -------------------------------------------------------------------------------- /public/fonts/gordita/Gordita-Ultra.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jutanium/solid-docs-next-next/210f30934d152612b633b8215defee5ce13a497e/public/fonts/gordita/Gordita-Ultra.woff -------------------------------------------------------------------------------- /public/global.css: -------------------------------------------------------------------------------- 1 | /* 2 | Josh's Custom CSS Reset 3 | https://www.joshwcomeau.com/css/custom-css-reset/ 4 | */ 5 | *, 6 | *::before, 7 | *::after { 8 | box-sizing: border-box; 9 | } 10 | 11 | * { 12 | margin: 0; 13 | } 14 | 15 | html, 16 | body { 17 | font-family: Gordita, ui-sans-serif, system-ui, -apple-system, 18 | BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, 19 | sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol, 20 | "Noto Color Emoji"; 21 | height: 100%; 22 | } 23 | 24 | body { 25 | line-height: 1.5; 26 | -webkit-font-smoothing: antialiased; 27 | } 28 | 29 | img, 30 | picture, 31 | video, 32 | canvas, 33 | svg { 34 | display: block; 35 | max-width: 100%; 36 | } 37 | 38 | input, 39 | button, 40 | textarea, 41 | select { 42 | font: inherit; 43 | } 44 | 45 | p, 46 | h1, 47 | h2, 48 | h3, 49 | h4, 50 | h5, 51 | h6 { 52 | overflow-wrap: break-word; 53 | } 54 | 55 | #root, 56 | #__next { 57 | isolation: isolate; 58 | } 59 | 60 | @font-face { 61 | font-family: "Gordita"; 62 | src: url("./fonts/gordita/Gordita-Regular.eot?#iefix") 63 | format("embedded-opentype"), 64 | url("./fonts/gordita/Gordita-Regular.woff") format("woff"), 65 | url("./fonts/gordita/Gordita-Regular.ttf") format("truetype"); 66 | font-style: normal; 67 | font-display: fallback; 68 | } 69 | 70 | @font-face { 71 | font-family: "Gordita"; 72 | src: url("./fonts/gordita/Gordita-Thin.eot?#iefix") 73 | format("embedded-opentype"), 74 | url("./fonts/gordita/Gordita-Thin.woff") format("woff"), 75 | url("./fonts/gordita/Gordita-Thin.ttf") format("truetype"); 76 | font-weight: 100; 77 | font-style: normal; 78 | font-display: fallback; 79 | } 80 | 81 | @font-face { 82 | font-family: "Gordita"; 83 | src: url("./fonts/gordita/Gordita-Medium.eot?#iefix") 84 | format("embedded-opentype"), 85 | url("./fonts/gordita/Gordita-Medium.woff") format("woff"), 86 | url("./fonts/gordita/Gordita-Medium.ttf") format("truetype"); 87 | font-weight: 600; 88 | font-style: normal; 89 | font-display: fallback; 90 | } 91 | 92 | @font-face { 93 | font-family: "Gordita"; 94 | src: url("./fonts/gordita/Gordita-Bold.eot?#iefix") 95 | format("embedded-opentype"), 96 | url("./fonts/gordita/Gordita-Bold.woff") format("woff"), 97 | url("./fonts/gordita/Gordita-Bold.ttf") format("truetype"); 98 | font-weight: 700; 99 | font-style: normal; 100 | font-display: fallback; 101 | } 102 | 103 | :root { 104 | /* Base Colors */ 105 | --black: #333; 106 | --white: #fff; 107 | --solid-blue: #4483c1; 108 | --solid-darkblue: #2c4f7c; 109 | --solid-lightblue: #b8d7ff; 110 | 111 | /* Theme Colors */ 112 | --text-base: var(--black); 113 | --text-secondary: var(--gray-5); 114 | --text-links: var(--solid-blue); 115 | --text-links-hover: var(--solid-darkblue); 116 | --bg-base: var(--white); 117 | --bg-secondary: var(--slate-1); 118 | --text-headings: var(--solid-darkblue); 119 | --outline-headings: var(--gray-2); 120 | --text-code: var(--gray-8); 121 | 122 | /* Tailwind Colors */ 123 | --gray-1: #f3f4f6; 124 | --gray-2: #e5e7eb; 125 | --gray-3: #d1d5db; 126 | --gray-4: #9ca3af; 127 | --gray-5: #6b7280; 128 | --gray-6: #4b5563; 129 | --gray-7: #374151; 130 | --gray-8: #1f2937; 131 | --gray-9: #111827; 132 | 133 | --slate-1: #f1f5f9; 134 | --slate-2: #e2e8f0; 135 | --slate-3: #cbd5e1; 136 | --slate-4: #94a3b8; 137 | --slate-5: #64748b; 138 | --slate-6: #475569; 139 | --slate-7: #334155; 140 | --slate-8: #1e293b; 141 | --slate-9: #0f172a; 142 | 143 | --neutral-1: #f5f5f5; 144 | --neutral-2: #e5e5e5; 145 | --neutral-3: #d4d4d4; 146 | --neutral-4: #a3a3a3; 147 | --neutral-5: #737373; 148 | --neutral-6: #525252; 149 | --neutral-7: #404040; 150 | --neutral-8: #262626; 151 | --neutral-9: #171717; 152 | 153 | /* Tailwind Spacing */ 154 | --spacing-0-5: 0.125rem; 155 | --spacing-1: 0.25rem; 156 | --spacing-1-5: 0.375rem; 157 | --spacing-2: 0.5rem; 158 | --spacing-2-5: 0.625rem; 159 | --spacing-3: 0.75rem; 160 | --spacing-3-5: 0.875rem; 161 | --spacing-4: 1rem; 162 | --spacing-5: 1.25rem; 163 | --spacing-6: 1.5rem; 164 | --spacing-7: 1.75rem; 165 | --spacing-8: 2rem; 166 | --spacing-9: 2.25rem; 167 | --spacing-10: 2.5rem; 168 | --spacing-11: 2.75rem; 169 | --spacing-12: 3rem; 170 | --spacing-14: 3.5rem; 171 | --spacing-16: 4rem; 172 | --spacing-20: 5rem; 173 | --spacing-24: 6rem; 174 | --spacing-28: 7rem; 175 | --spacing-32: 8rem; 176 | --spacing-36: 9rem; 177 | --spacing-40: 10rem; 178 | --spacing-44: 11rem; 179 | --spacing-48: 12rem; 180 | --spacing-52: 13rem; 181 | --spacing-56: 14rem; 182 | --spacing-60: 15rem; 183 | --spacing-64: 16rem; 184 | --spacing-72: 18rem; 185 | --spacing-80: 20rem; 186 | --spacing-96: 24rem; 187 | 188 | /* Tailwind Sizes */ 189 | --text-xs: 0.75rem; 190 | --text-sm: 0.875rem; 191 | --text-default: 1rem; 192 | --text-lg: 1.125rem; 193 | --text-xl: 1.25rem; 194 | --text-2xl: 1.5rem; 195 | --text-3xl: 2.25rem; 196 | --text-4xl: 2.5rem; 197 | --text-5xl: 3rem; 198 | --text-6xl: 3.75rem; 199 | --text-7xl: 4.5rem; 200 | --text-8xl: 6rem; 201 | --text-9xl: 8rem; 202 | } 203 | 204 | :root[data-theme="dark"] { 205 | --text-base: var(--white); 206 | --text-secondary: var(--gray-3); 207 | --bg-base: var(--neutral-8); 208 | --bg-secondary: var(--neutral-9); 209 | --text-headings: var(--solid-lightblue); 210 | --outline-headings: var(--neutral-6); 211 | --text-links: var(--solid-lightblue); 212 | --text-links-hover: var(--solid-blue); 213 | --text-code: var(--neutral-9); 214 | } 215 | 216 | body { 217 | background-color: var(--bg-base); 218 | } 219 | 220 | .prose { 221 | color: var(--text-base); 222 | font-size: 1rem; 223 | line-height: var(--spacing-7); 224 | } 225 | .divider, 226 | .prose :is(h1, h2) { 227 | line-height: 1; 228 | border-bottom: 1px solid var(--outline-headings); 229 | padding-bottom: var(--spacing-2); 230 | margin-bottom: var(--spacing-6); 231 | } 232 | 233 | .prose :is(h1, h2, h3, h4) { 234 | font-weight: 600; 235 | color: var(--text-headings); 236 | margin-top: var(--spacing-10); 237 | } 238 | 239 | .prose h1 { 240 | font-size: var(--text-3xl); 241 | } 242 | 243 | .prose h3 { 244 | font-size: var(--text-xl); 245 | margin-bottom: var(--spacing-3); 246 | } 247 | 248 | .prose h4 { 249 | margin-bottom: var(--spacing-2); 250 | } 251 | 252 | .prose :is(ul, ol) { 253 | padding-left: var(--spacing-8); 254 | } 255 | 256 | .prose li { 257 | margin-block: var(--spacing-2); 258 | } 259 | 260 | .prose li::marker { 261 | color: var(--gray-5); 262 | } 263 | 264 | .prose p { 265 | margin-block: var(--spacing-5); 266 | } 267 | 268 | .prose a { 269 | color: var(--text-links); 270 | } 271 | 272 | .prose aside a { 273 | --text-links: var(--solid-lightblue); 274 | --text-links-hover: var(--solid-blue); 275 | } 276 | 277 | .prose a:is(:hover, :focus) { 278 | color: var(--text-links-hover); 279 | } 280 | 281 | .prose code { 282 | padding: var(--spacing-0-5); 283 | background-color: rgb(203 213 225 / 0.2); 284 | border-radius: var(--spacing-1-5); 285 | } 286 | 287 | .prose pre code { 288 | background: none; 289 | } 290 | 291 | .prose pre { 292 | /* Override github-dark default background */ 293 | background-color: var(--text-code) !important; 294 | margin-bottom: var(--spacing-7); 295 | padding: var(--spacing-4) var(--spacing-3-5); 296 | } 297 | 298 | .prose img, 299 | video { 300 | width: 100%; 301 | } 302 | -------------------------------------------------------------------------------- /public/logos/solid-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 30 | 31 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/logos/solid-logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 26 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/Aside.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import IconBulb from "~icons/heroicons-solid/light-bulb"; 3 | import IconBrain from "~icons/mdi/brain"; 4 | import IconPencil from "~icons/mdi/lead-pencil"; 5 | import IconAlertDecagram from "~icons/mdi/alert-decagram"; 6 | import IconReact from "~icons/mdi/react"; 7 | import IconVue from "~icons/mdi/vuejs"; 8 | import IconAngular from "~icons/mdi/angular"; 9 | import IconSvelte from "~icons/simple-icons/svelte"; 10 | 11 | import type { JSX } from "solid-js/types/jsx"; 12 | 13 | // prettier-ignore 14 | export type asideType = "vue" | "react" | "angular" | "svelte" | "note" | "warning" | "tip" |"advanced"; 15 | 16 | export interface Props { 17 | type: asideType; 18 | title?: string; 19 | } 20 | 21 | const asides: { 22 | [key in asideType]: { 23 | title: string; 24 | logo: typeof IconBrain; 25 | }; 26 | } = { 27 | vue: { 28 | title: "Since you're coming from Vue", 29 | logo: IconVue, 30 | }, 31 | react: { 32 | title: "Since you're coming from React", 33 | logo: IconReact, 34 | }, 35 | angular: { 36 | title: "Since you're coming from Angular", 37 | logo: IconAngular, 38 | }, 39 | svelte: { 40 | title: "Since you're coming from Svelte", 41 | logo: IconSvelte, 42 | }, 43 | note: { 44 | title: "Note", 45 | logo: IconBulb, 46 | }, 47 | warning: { 48 | title: "Warning", 49 | logo: IconAlertDecagram, 50 | }, 51 | tip: { 52 | title: "Tip", 53 | logo: IconPencil, 54 | }, 55 | advanced: { 56 | title: "Advanced", 57 | logo: IconBrain, 58 | }, 59 | }; 60 | 61 | const { type = "note", title } = Astro.props as Props; 62 | 63 | const Logo = asides[type].logo; 64 | --- 65 | 66 | 75 | 76 | 93 | -------------------------------------------------------------------------------- /src/components/CodeSample/CodeSample.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import CodeSample from "./CodeSample.tsx"; 3 | --- 4 | 5 | 6 |

7 | 8 |

9 |

10 | 11 |

12 |
13 | -------------------------------------------------------------------------------- /src/components/CodeSample/CodeSample.module.css: -------------------------------------------------------------------------------- 1 | .codeSample { 2 | margin-block: var(--spacing-5); 3 | --javascript-background: #f7df1e; 4 | --typescript-background: #3178c6; 5 | } 6 | 7 | .tabList { 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: flex-end; 11 | gap: var(--spacing-1); 12 | margin-bottom: var(--spacing-4); 13 | } 14 | 15 | .codeSample astro-slot p { 16 | margin: 0; 17 | } 18 | 19 | .codeSample pre { 20 | margin-top: 0; 21 | } 22 | 23 | .tabList button { 24 | color: var(--text-base); 25 | font-weight: 700; 26 | padding-inline: var(--spacing-3); 27 | padding-block: var(--spacing-2); 28 | background: transparent; 29 | border: 0; 30 | border-radius: var(--spacing-1); 31 | cursor: pointer; 32 | } 33 | 34 | .tabList button[aria-selected="true"][data-key="js"] { 35 | color: var(--black); 36 | background-color: var(--javascript-background); 37 | } 38 | 39 | .tabList button[aria-selected="true"][data-key="ts"] { 40 | background-color: var(--typescript-background); 41 | color: var(--white); 42 | } 43 | -------------------------------------------------------------------------------- /src/components/CodeSample/CodeSample.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "@kobalte/core"; 2 | import type { JSXElement } from "solid-js"; 3 | import preferences from "../Preferences/usePreferences"; 4 | import styles from "./CodeSample.module.css"; 5 | 6 | export default function CodeSample(props: { js: JSXElement; ts: JSXElement }) { 7 | const [{ jsTs }, { setJsTs }] = preferences; 8 | 9 | return ( 10 | setJsTs(val as "js" | "ts")} 14 | > 15 | 16 | JS 17 | TS 18 | 19 | {props.js} 20 | {props.ts} 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/CodeSample/CodeSampleCompile.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import CodeSample from "./CodeSample.astro"; 3 | import { tsToJs, getTscOptions } from "./compileTS.ts"; 4 | import { readFile } from "node:fs/promises"; 5 | import CodeSnippet from "../CodeSnippet/CodeSnippet.astro"; 6 | import { Code } from "astro/components"; 7 | import { preprocessCode } from "integrations/code-snippets.ts"; 8 | 9 | export interface Props { 10 | jsTitle?: string; 11 | tsTitle?: string; 12 | tsPath: string; 13 | } 14 | 15 | const { tsPath, jsTitle, tsTitle } = Astro.props; 16 | 17 | const loadedTs = await readFile(tsPath); 18 | if (!loadedTs) { 19 | throw new Error(`Could not load ${tsPath}`); 20 | } 21 | 22 | const ts = preprocessCode(loadedTs.toString()); 23 | const js = preprocessCode(await tsToJs(tsPath, getTscOptions())); 24 | --- 25 | 26 | 27 | { 28 | js && ( 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/components/CodeSample/compileTS.ts: -------------------------------------------------------------------------------- 1 | import ts from "typescript"; 2 | import { ESLint } from "eslint"; 3 | import { readFile, unlink, access } from "node:fs/promises"; 4 | import prettier from "prettier"; 5 | 6 | export function getTscOptions(): ts.CompilerOptions { 7 | const configFile = ts.findConfigFile( 8 | process.cwd(), 9 | ts.sys.fileExists, 10 | "tsconfig.json" 11 | ); 12 | if (!configFile) throw Error("tsconfig.json not found"); 13 | const { config } = ts.readConfigFile(configFile, ts.sys.readFile); 14 | const { options } = ts.parseJsonConfigFileContent( 15 | config, 16 | ts.sys, 17 | process.cwd() 18 | ); 19 | 20 | return { 21 | ...options, 22 | emitDeclarationOnly: false, 23 | noEmit: false, 24 | noEmitOnError: false, 25 | // packages from paths are being inlined to the output 26 | paths: {}, 27 | }; 28 | } 29 | 30 | export async function tsToJs( 31 | entryFile: string, 32 | options: ts.CompilerOptions, 33 | oldProgram?: ts.Program 34 | ) { 35 | const program = ts.createProgram([entryFile], options, undefined, oldProgram); 36 | program.emit(); 37 | 38 | // ESlint doesn't do anything yet, but we're going to make a lint rule that adds line breaks between 39 | // functions. (Prettier can't do that) 40 | const eslint = new ESLint({ 41 | fix: true, 42 | useEslintrc: false, 43 | overrideConfig: { 44 | plugins: ["solid"], 45 | extends: ["eslint:recommended", "plugin:solid/recommended"], 46 | parserOptions: { 47 | sourceType: "module", 48 | ecmaVersion: "latest", 49 | }, 50 | rules: { 51 | "padding-line-between-statements": "error", 52 | "padded-blocks": "error", 53 | }, 54 | env: { 55 | es2022: true, 56 | node: true, 57 | }, 58 | }, 59 | }); 60 | 61 | try { 62 | const newPath = entryFile.replace(".ts", ".js"); 63 | 64 | const results = await eslint.lintFiles(newPath); 65 | 66 | await ESLint.outputFixes(results); 67 | const read = await readFile(newPath); 68 | const formatted = prettier.format(read.toString(), { parser: "babel" }); 69 | 70 | try { 71 | await access(newPath); 72 | await unlink(newPath); 73 | } catch (e) {} 74 | 75 | return formatted; 76 | } catch (e) { 77 | console.error(e); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/CodeSnippet/CodeSnippet.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import IconClipboard from "~icons/heroicons-solid/clipboard-document"; 3 | import IconClipboardChecked from "~icons/heroicons-solid/clipboard-document-check"; 4 | 5 | interface Props { 6 | title?: string; 7 | code?: string; 8 | } 9 | 10 | const { title = "", code = "" } = Astro.props; 11 | 12 | // Generate HTML code from the title (if any), improving the ability to wrap long file paths 13 | // into multiple lines by inserting a line break opportunity after each slash 14 | const titleHtml = decodeURIComponent(title).replace(/([\\/])/g, "$1"); 15 | 16 | const codeSnippetHtml = await Astro.slots.render("default"); 17 | --- 18 | 19 |
20 |
21 | 22 | 44 |
45 | 46 |
47 | 92 | -------------------------------------------------------------------------------- /src/components/FrameworkAside.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import FrameworkAside from "./FrameworkAside.tsx"; 3 | import Aside from "./Aside.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/FrameworkAside.tsx: -------------------------------------------------------------------------------- 1 | import type { JSX } from "solid-js/jsx-runtime"; 2 | 3 | import preferences from "./Preferences/usePreferences"; 4 | import { Match, Switch } from "solid-js"; 5 | import type { framework } from "./Preferences/usePreferences"; 6 | 7 | type Framework = Exclude; 8 | 9 | type Props = { [K in Framework]?: JSX.Element }; 10 | export default function FrameworkAside(props: Props) { 11 | const [{ framework }, { setFramework }] = preferences; 12 | 13 | return ( 14 | 15 | {Object.keys(props).map((f) => ( 16 | {props[f as Framework]} 17 | ))} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Header/Header.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import ThemeToggle from "./ThemeToggle"; 3 | import MobileNavToggle from "./MobileNavToggle"; 4 | import Logo from "./Logo.astro"; 5 | --- 6 | 7 |
8 | 9 |
10 | 11 | 12 |
13 |
14 | 15 | 40 | -------------------------------------------------------------------------------- /src/components/Header/Logo.astro: -------------------------------------------------------------------------------- 1 |
2 | 8 | 9 | 52 |
53 | 54 | 70 | -------------------------------------------------------------------------------- /src/components/Header/MobileNavToggle.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | display: none; 3 | background: none; 4 | border: none; 5 | cursor: pointer; 6 | border: 1px solid transparent; 7 | } 8 | 9 | .button svg { 10 | color: var(--text-base); 11 | } 12 | 13 | :global(.mobile-menu) .button { 14 | border-color: var(--text-base); 15 | border-radius: var(--spacing-1); 16 | } 17 | 18 | @media (max-width: 50em) { 19 | .button { 20 | display: inline; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Header/MobileNavToggle.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createEffect, createSignal } from "solid-js"; 2 | import IconHamburger from "~icons/heroicons-solid/bars-3"; 3 | import { ToggleButton } from "@kobalte/core"; 4 | import styles from "./MobileNavToggle.module.css"; 5 | 6 | const MobileNavToggle: Component = () => { 7 | const [isOpen, setIsOpen] = createSignal(false); 8 | 9 | createEffect(() => { 10 | if (isOpen()) { 11 | document.body.classList.add("mobile-menu"); 12 | } else { 13 | document.body.classList.remove("mobile-menu"); 14 | } 15 | }); 16 | 17 | function toggleMenu() { 18 | setIsOpen(!isOpen()); 19 | } 20 | 21 | return ( 22 | 28 | 30 | ); 31 | }; 32 | 33 | export default MobileNavToggle; 34 | -------------------------------------------------------------------------------- /src/components/Header/ThemeToggle.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background: none; 3 | border: none; 4 | cursor: pointer; 5 | } 6 | 7 | .button svg { 8 | color: var(--text-base); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Header/ThemeToggle.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createEffect, createSignal, onMount } from "solid-js"; 2 | import IconSun from "~icons/heroicons-solid/sun"; 3 | import IconMoon from "~icons/heroicons-solid/moon"; 4 | import { ToggleButton } from "@kobalte/core"; 5 | import styles from "./ThemeToggle.module.css"; 6 | 7 | const ThemeToggle: Component = () => { 8 | const [isDark, setIsDark] = createSignal(false); 9 | 10 | onMount(() => { 11 | setIsDark(localStorage.getItem("theme") === "dark"); 12 | }); 13 | 14 | createEffect(() => { 15 | if (isDark()) { 16 | document.documentElement.setAttribute("data-theme", "dark"); 17 | } else { 18 | document.documentElement.setAttribute("data-theme", "light"); 19 | } 20 | }); 21 | 22 | function toggleTheme() { 23 | setIsDark(!isDark()); 24 | localStorage.setItem("theme", isDark() ? "dark" : "light"); 25 | } 26 | 27 | return ( 28 | 34 | {(state) => 35 | state.isPressed() ? ( 36 | 42 | ); 43 | }; 44 | 45 | export default ThemeToggle; 46 | -------------------------------------------------------------------------------- /src/components/LeftSidebar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getSidebarEntries } from "../sidebar"; 3 | import type { SidebarEntry } from "../sidebar"; 4 | import Preferences from "~/components/Preferences/Preferences.tsx"; 5 | 6 | export interface Props { 7 | currentPage: string; 8 | } 9 | 10 | const { currentPage } = Astro.props; 11 | const sidebar = getSidebarEntries(); 12 | 13 | function isCurrent(slug: string) { 14 | return currentPage.replace(/\/$/, "").endsWith(slug); 15 | } 16 | 17 | function routeIncludesChild(children: SidebarEntry[]) { 18 | return children.some((child) => isCurrent(child.slug)); 19 | } 20 | --- 21 | 22 | 54 | 55 | 127 | -------------------------------------------------------------------------------- /src/components/Preferences/IfTS.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import IfTS from "./IfTS.tsx"; 3 | import { Code } from "astro/components"; 4 | 5 | export interface Props { 6 | js?: string; 7 | code?: boolean; 8 | } 9 | 10 | const { js, code } = Astro.props; 11 | 12 | const ts = await Astro.slots.render("default"); 13 | --- 14 | 15 | 16 | 17 | {code && js ? : } 18 | 19 | {code && ts ? : } 20 | 21 | -------------------------------------------------------------------------------- /src/components/Preferences/IfTS.tsx: -------------------------------------------------------------------------------- 1 | import { JSXElement, Show } from "solid-js"; 2 | import type { ParentProps } from "solid-js"; 3 | import preferences from "./usePreferences"; 4 | 5 | export default function IfTS(props: ParentProps<{ fallback?: JSXElement }>) { 6 | const [{ jsTs }] = preferences; 7 | 8 | return ( 9 | 10 | {props.children} 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Preferences/Preferences.module.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --preferences-background: var(--slate-1); 3 | --preferences-icon-background: var(--neutral-3); 4 | --preferences-border: var(--neutral-4); 5 | --preferences-selected: var(--solid-blue); 6 | } 7 | 8 | :root[data-theme="dark"] { 9 | --preferences-background: var(--neutral-9); 10 | --preferences-icon-background: var(--neutral-7); 11 | --preferences-border: var(--neutral-6); 12 | --preferences-selected: var(--solid-darkblue); 13 | } 14 | 15 | .preferencesDetails { 16 | background-color: var(--preferences-background); 17 | color: var(--text-base); 18 | width: 100%; 19 | margin-bottom: var(--spacing-10); 20 | padding-block: var(--spacing-4); 21 | border: 1px solid var(--preferences-border); 22 | border-radius: var(--spacing-2); 23 | } 24 | 25 | .preferencesDetails:focus-within { 26 | outline: 2px solid var(--preferences-border); 27 | } 28 | 29 | .preferencesDetails summary { 30 | display: flex; 31 | flex-direction: row; 32 | align-items: center; 33 | justify-content: space-between; 34 | list-style: none; 35 | cursor: pointer; 36 | padding-inline: var(--spacing-4); 37 | outline: none; 38 | } 39 | 40 | .summaryContainer { 41 | display: flex; 42 | flex-direction: row; 43 | align-items: center; 44 | gap: var(--spacing-2); 45 | } 46 | 47 | .preferencesDetails[open] .chevron { 48 | transform: rotate(90deg); 49 | } 50 | 51 | .chevron { 52 | width: 24px; 53 | height: 24px; 54 | color: var(--neutral-5); 55 | transform: rotate(0); 56 | transition: transform 0.15s ease; 57 | } 58 | 59 | .iconBackground { 60 | padding: var(--spacing-1); 61 | background-color: var(--preferences-icon-background); 62 | border-radius: var(--spacing-2); 63 | } 64 | 65 | .preferencesDetails summary::-webkit-details-marker { 66 | display: none; 67 | } 68 | 69 | .preferencesDivider { 70 | border-top: 1px solid var(--preferences-border); 71 | margin-top: var(--spacing-4); 72 | padding-top: var(--spacing-4); 73 | padding-inline: var(--spacing-4); 74 | } 75 | 76 | .radioLabel { 77 | font-size: var(--text-sm); 78 | } 79 | 80 | .groupContainer { 81 | display: flex; 82 | flex-direction: row; 83 | align-items: center; 84 | flex-wrap: wrap; 85 | gap: var(--spacing-2); 86 | margin-block: var(--spacing-4); 87 | } 88 | 89 | .groupContainer label { 90 | text-transform: capitalize; 91 | font-size: var(--text-sm); 92 | display: flex; 93 | flex-direction: row; 94 | align-items: center; 95 | flex: 1; 96 | min-width: 100px; 97 | gap: var(--spacing-2); 98 | padding: var(--spacing-2); 99 | border: 1px solid var(--preferences-border); 100 | border-radius: var(--spacing-1); 101 | cursor: pointer; 102 | } 103 | 104 | .groupContainer label:focus-within { 105 | outline: 2px solid var(--preferences-selected); 106 | } 107 | 108 | .groupContainer div { 109 | flex-shrink: 0; 110 | } 111 | 112 | .groupContainer label.selected { 113 | border-color: var(--preferences-selected); 114 | } 115 | -------------------------------------------------------------------------------- /src/components/Preferences/Preferences.tsx: -------------------------------------------------------------------------------- 1 | import { For, createEffect } from "solid-js"; 2 | import { RadioGroup } from "@kobalte/core"; 3 | import type { Component } from "solid-js"; 4 | import IconJs from "~icons/logos/javascript"; 5 | import IconTs from "~icons/logos/typescript-icon"; 6 | import IconReact from "~icons/vscode-icons/file-type-reactjs"; 7 | import IconVue from "~icons/vscode-icons/file-type-vue"; 8 | import IconSvelte from "~icons/vscode-icons/file-type-svelte"; 9 | import IconAngular from "~icons/vscode-icons/file-type-angular"; 10 | import IconNone from "~icons/heroicons-outline/ban"; 11 | import IconChevron from "~icons/heroicons-outline/chevron-right"; 12 | import IconGear from "~icons/heroicons-solid/cog"; 13 | import { Dynamic } from "solid-js/web"; 14 | import preferences, { frameworks } from "./usePreferences"; 15 | import type { framework } from "./usePreferences"; 16 | 17 | import styles from "./Preferences.module.css"; 18 | 19 | const Preferences: Component = () => { 20 | const [{ jsTs, framework }, { setJsTs, setFramework }] = preferences; 21 | 22 | // const [jsTs, _setJsTs] = createSignal<"js" | "ts">(); 23 | // const [framework, _setFramework] = createSignal("none"); 24 | 25 | // const setJsTs = (value: "js" | "ts") => { 26 | // _setJsTs(value); 27 | // localStorage.setItem("jsTs", value); 28 | // }; 29 | 30 | // const setFramework = (value: framework) => { 31 | // _setFramework(value); 32 | // localStorage.setItem("framework", value); 33 | // }; 34 | 35 | // onMount(() => { 36 | // setJsTs((localStorage.getItem("jsTs") as "js" | "ts") || "js"); 37 | // setFramework((localStorage.getItem("framework") as framework) || "none"); 38 | // }); 39 | 40 | const icons = { 41 | react: IconReact as Component, 42 | vue: IconVue as Component, 43 | angular: IconAngular as Component, 44 | svelte: IconSvelte as Component, 45 | none: IconNone as Component, 46 | }; 47 | 48 | return ( 49 |
50 | 51 |
52 |
53 |
55 | Preferences 56 |
57 | 58 |
59 |
60 | setJsTs(val as "js" | "ts")} 63 | > 64 | 65 | Do you prefer JavaScript or TypeScript? 66 | 67 |
68 | 72 | 73 | 74 | 77 | JavaScript 78 | 79 | 83 | 84 | 85 | 88 | TypeScript 89 | 90 |
91 |
92 | setFramework(val as framework)} 95 | > 96 | 97 | Are you coming from another framework? 98 | 99 |
100 | 101 | {(value) => ( 102 | 106 | 107 | 108 | 111 | {value} 112 | 113 | )} 114 | 115 |
116 |
117 |
118 |
119 | ); 120 | }; 121 | 122 | export default Preferences; 123 | -------------------------------------------------------------------------------- /src/components/Preferences/usePreferences.ts: -------------------------------------------------------------------------------- 1 | import { createLocalStorage } from "@solid-primitives/storage"; 2 | 3 | const [store, setStore] = createLocalStorage({ 4 | prefix: "solid-docs-next-next", 5 | }); 6 | 7 | export const frameworks = [ 8 | "react", 9 | "vue", 10 | "angular", 11 | "svelte", 12 | "none", 13 | ] as const; 14 | export type framework = (typeof frameworks)[number]; 15 | export type jsTs = "js" | "ts"; 16 | 17 | function setFramework(framework: framework) { 18 | setStore("framework", framework); 19 | } 20 | function setJsTs(jsTs: jsTs) { 21 | setStore("jsTs", jsTs); 22 | } 23 | 24 | function framework(): framework { 25 | return store.framework as framework; 26 | } 27 | 28 | function jsTs(): jsTs { 29 | return store.jsTs as jsTs; 30 | } 31 | 32 | export default [ 33 | { framework, jsTs }, 34 | { setFramework, setJsTs }, 35 | ] as const; 36 | -------------------------------------------------------------------------------- /src/components/RightSidebar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { MarkdownHeading } from "astro"; 3 | 4 | interface Props { 5 | headings: MarkdownHeading[]; 6 | } 7 | 8 | const { headings } = Astro.props; 9 | const toc = headings.slice(1); 10 | --- 11 | 12 | 26 | 27 | 75 | -------------------------------------------------------------------------------- /src/components/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { createMutationObserver } from "@solid-primitives/mutation-observer"; 2 | import { createSignal } from "solid-js"; 3 | 4 | // https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts 5 | 6 | const [isDark, setIsDark] = createSignal(false); 7 | 8 | createMutationObserver( 9 | () => document.documentElement, 10 | { attributes: true }, 11 | (mutations) => { 12 | mutations.forEach((mutation) => { 13 | if (mutation.attributeName === "data-theme") { 14 | setIsDark( 15 | document.documentElement.getAttribute("data-theme") === "dark" 16 | ); 17 | } 18 | }); 19 | } 20 | ); 21 | 22 | export { isDark }; 23 | -------------------------------------------------------------------------------- /src/content.ts: -------------------------------------------------------------------------------- 1 | import { getCollection } from "astro:content"; 2 | 3 | export const allPages = await getCollection("docs"); 4 | -------------------------------------------------------------------------------- /src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { z, defineCollection } from "astro:content"; 2 | 3 | const docs = defineCollection({ 4 | schema: z.object({ 5 | title: z.string(), 6 | draft: z.boolean().default(false), 7 | }), 8 | }); 9 | 10 | export const collections = { docs }; 11 | -------------------------------------------------------------------------------- /src/content/docs/concepts/tracking.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Head and metadata 3 | active: true 4 | --- 5 | 6 | **Tracking** is the mechanism that Solid uses to "keep track" of where a signal 7 | was accessed. This is how it knows which effects to rerun when a given signal 8 | changes. 9 | 10 | ## What you need to know 11 | 12 | 1. The goal of tracking is to register subscriptions. When an effect 13 | "subscribes" to a signal, it will rerun when that signal changes. 14 | 2. Tracking happens on access. Whenever a signal (or store value) is accessed 15 | inside an effect, the signal is "tracked" and the effect is subscribed to the 16 | signal. 17 | 3. Tracking doesn't happen everywhere. If you access a signal outside an effect, 18 | no tracking will happen. That's because there's no effect to subscribe to the 19 | signal. 20 | 4. The **tracking rule of thumb**: _access_ a reactive value at the same time 21 | that you _use_ it. 22 | 23 | This doesn't just apply to signals created with `createSignal` - props and 24 | stores work the same way. 25 | 26 | ## Example 27 | 28 | ```jsx 29 | function Counter() { 30 | const [count, setCount] = createSignal(0); 31 | const increment = () => setCount(count() + 1); 32 | 33 | /* When count() is called, the "count" signal is tracked as a subscription of this effect, 34 | so this code reruns when (and only when) count changes */ 35 | createEffect(() => { 36 | console.log("My effect says " + count()); 37 | }); 38 | 39 | /* This code isn't in a "tracking scope", so count() 40 | doesn't do anything special and this code never reruns */ 41 | console.log(count()); 42 | 43 | return ( 44 | /* 45 | JSX is a tracking scope (it uses effects behind the scenes), so this code 46 | registers count as a subscription when count() is called 47 | */ 48 | 51 | ); 52 | } 53 | ``` 54 | 55 | ## Tracking Gotchas 56 | 57 | Let's look at some examples where we can apply **the tracking rule of thumb** to 58 | debug some code. 59 | 60 | ### Derived State 61 | 62 | In this example, the paragraph won't update when `count` changes, because 63 | `count` was accessed outside of a tracking scope. 64 | 65 | ```jsx 66 | function Counter() { 67 | const [count, setCount] = createSignal(0); 68 | const increment = () => setCount(count() + 1); 69 | 70 | const doubleCount = count() * 2; 71 | return ( 72 | <> 73 |

Twice my count: {doubleCount}

74 | 77 | 78 | ); 79 | } 80 | ``` 81 | 82 | The solution is to turn `doubleCount` into a _function_. That way, when 83 | `doubleCount` is used in the JSX, `count()` will be called and a subscription 84 | will be registered. 85 | 86 | ```jsx 87 | function Counter() { 88 | const [count, setCount] = createSignal(0); 89 | const increment = () => setCount(count() + 1); 90 | 91 | const doubleCount = () => count() * 2; 92 | return ( 93 | <> 94 |

Twice my count: {doubleCount()}

95 | 98 | 99 | ); 100 | } 101 | ``` 102 | 103 | ### Destructuring Props 104 | 105 | In this example, the paragraph in DoubleCountView will never update. 106 | 107 | ```jsx 108 | function DoubleCountView(props) { 109 | const { value } = props; 110 | const doubleCount = () => value * 2; 111 | return

{doubleCount()}

; 112 | } 113 | 114 | function Counter() { 115 | const [count, setCount] = createSignal(0); 116 | const increment = () => setCount(count() + 1); 117 | 118 | return ( 119 | <> 120 | 121 | 124 | 125 | ); 126 | } 127 | ``` 128 | 129 | Here, the reactive value is the `value` prop, passed from the parent. Where is 130 | that value accessed? 131 | 132 | ```jsx 133 | const { value } = props; 134 | ``` 135 | 136 | This destructuring assignment is the only code that actually accesses 137 | `props.value`! After that, `value` just represents the static value that was 138 | accessed at that time. 139 | 140 | To fix this, we need to make sure we _access_ the value at the same time that we 141 | _use_ it. We can call `props.value` directly: 142 | 143 | ```jsx 144 | function DoubleCountView(props) { 145 | const doubleCount = () => props.value * 2; 146 | return

{doubleCount()}

; 147 | } 148 | ``` 149 | 150 | Alternatively, we could destructure `props` at the same time that we use it: 151 | 152 | ```jsx 153 | function DoubleCountView(props) { 154 | const doubleCount = () => { 155 | const { value } = props; 156 | return value * 2; 157 | }; 158 | return

{doubleCount()}

; 159 | } 160 | ``` 161 | -------------------------------------------------------------------------------- /src/content/docs/start-here/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Start Here 3 | --- 4 | import CodeSampleCompile from "~/components/CodeSample/CodeSampleCompile.astro" 5 | 6 | Testing CodeSampleCompile: 7 | 8 | 9 | 10 | Welcome! -------------------------------------------------------------------------------- /src/content/docs/start-here/js-for-solid.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: JavaScript for Solid 3 | --- 4 | Are you new to frontend frameworks or coming back to JavaScript after a few years? Here's a refresher on the JavaScript features and patterns you need to know. JavaScript is a flexible language and there are many approaches to writing it, so this isn't a conclusive manual for all JavaScript codebases. It's a "best practices" guide for working with Solid. 5 | 6 | ## Avoid these keywords 7 | 8 | ```js 9 | var, this, class 10 | ``` 11 | 12 | When working with Solid, it's a good idea to avoid these. You can build any Solid app without them, they add complexity to your code, and there are replacements for them. 13 | 14 | ## `let` and `const` 15 | 16 | You may have used `var` in JavaScript for declaring a variable. In modern JavaScript, there are two keywords for declaring variables: `let` and `const`. `const` declares a variable that won't ever be set to anything else, and `let` declares a variable that _can_ be assigned to something else. 17 | 18 | ```js 19 | let counter = 2; 20 | counter++; // counter is now set to 3 21 | const badCounter = 2; 22 | badCounter++; // TypeError: Assignment to constant variable. 23 | 24 | const person = { name: "Ryan" }; 25 | 26 | // This works; you haven't reassigned `person`, you simply 27 | // mutated the object that was stored there. 28 | person.name = "Joe"; 29 | 30 | const people = [person]; 31 | 32 | // This works; you haven't reassigned `people`, you simply added 33 | // something to the array that was stored there. 34 | people.push({ name: "David" }); 35 | ``` 36 | 37 | When working with objects and arrays, it is far more common to use `const` than `let`, because usually you want to mutate the object (`person.name = "Joe"`) rather than reassigning the variable (`person = { name: "Joe"}`). 38 | 39 | By default, use `const`. If you find that a variable does need to be reassigned later, change it to `let`. This makes your intention clear. 40 | 41 | ## Vite and ES6 Imports 42 | 43 | Before modern toolchains for web development, you would "import" multiple JavaScript files by including `script` tags in our HTML. 44 | 45 | ```html title="index.html" 46 | 47 | 48 | ``` 49 | 50 | This was tricky because `utils.js` executed first, so it couldn't refer to functions in `main.js`. The order of the script tags mattered. You also had to declare these scripts in your HTML rather than in your JavaScript, where they were most likely to be used. 51 | 52 | Developer tools like [Vite](https://vitejs.dev/) use a better approach to importing JavaScript. Vite is a development server and a bundling tool. It provides features like automatic reloading that makes it easier to develop your project. It supports the JavaScript-based [approach to imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) that was introduced in the ES6 version of the language. 53 | 54 | ```js title="src/utils.js" 55 | function areaOfCircle(radius) { 56 | return Math.PI * radius ** 2; 57 | } 58 | 59 | export { areaOfCircle }; 60 | ``` 61 | 62 | ```js title="src/main.js" 63 | import { areaOfCircle } from "./utils.js"; 64 | 65 | console.log(areaOfCircle(4)); 66 | ``` 67 | 68 | When you're ready to deploy your site, Vite "builds" your code, optimizing it and splitting your JavaScript back into script tags. 69 | 70 | When writing Solid projects, use Vite. 71 | 72 | ## Factory Functions and Closures 73 | 74 | You can create a function that returns an object for reusable functionality and state. 75 | 76 | ```js 77 | function createCar(make, year) { 78 | const currentYear = new Date().getFullYear(); 79 | const age = currentYear - year; 80 | 81 | return { 82 | make, 83 | age, 84 | }; 85 | } 86 | 87 | const myCar = createCar("Ford Fusion", 2018); 88 | console.log(myCar.age); 89 | ``` 90 | 91 | Because functions in JavaScript can be passed around like any other value (i.e. JavaScript has _first-class functions_), the object created by the factory function can have its own functions: 92 | 93 | ```js 94 | function createCar(make, year) { 95 | const currentYear = new Date().getFullYear(); 96 | const age = currentYear - year; 97 | 98 | function drive() { 99 | console.log(make + " goes VROOM"); 100 | } 101 | 102 | return { 103 | make, 104 | age, 105 | drive, 106 | }; 107 | } 108 | 109 | const myCar = createCar("Ford Fusion", 2018); 110 | myCar.drive(); //Ford Fusion goes VROOM 111 | ``` 112 | 113 | Note that the `drive` function uses the value of `make`, which was a parameter of the outer function. Later, when `myCar.drive()` is called, it "remembers" the value of `make`. This is called a _closure_ because a value outside of the function was "enclosed" with the function so that it can make use of it later on. 114 | 115 | We can also use these ideas to write factories that directly return functions: 116 | 117 | ```js 118 | function multiplyBy(multiplier) { 119 | return function (number) { 120 | return multiplier * number; 121 | }; 122 | } 123 | 124 | const multiplicator = multiplyBy(2); 125 | console.log(multiplicator(7)); //14 126 | ``` 127 | 128 | ## Getters 129 | 130 | The last example showed a `createCar` factory function. But what if it let you change the car's year? 131 | 132 | ```js 133 | function createCar(make, year) { 134 | const currentYear = new Date().getFullYear(); 135 | const age = currentYear - year; 136 | 137 | function drive() { 138 | console.log(make + " goes VROOM"); 139 | } 140 | 141 | function setYear(newYear) { 142 | year = newYear; 143 | } 144 | 145 | return { 146 | make, 147 | age, 148 | drive, 149 | setYear 150 | }; 151 | } 152 | 153 | const myCar = createCar("Ford Fusion", 2018); 154 | console.log(myCar.age); // 5 155 | myCar.setYear(2015); 156 | console.log(myCar.age); // 5 157 | ``` 158 | 159 | The year was updated, but the `age` wasn't. That's because the `age` calculation (`age = currentYear - year`) only happens when the `createCar` function is called. 160 | 161 | Instead, you can make `age` a function that calculates the `age` when it is called: 162 | 163 | ```js 164 | function createCar(make, year) { 165 | const currentYear = new Date().getFullYear(); 166 | 167 | function age () { 168 | return currentYear - year; 169 | } 170 | 171 | function drive() { 172 | console.log(make + " goes VROOM"); 173 | } 174 | 175 | function setYear(newYear) { 176 | year = newYear; 177 | } 178 | 179 | return { 180 | make, 181 | age, 182 | drive, 183 | setYear 184 | }; 185 | } 186 | 187 | const myCar = createCar("Ford Fusion", 2018); 188 | console.log(myCar.age()); // 5 189 | myCar.setYear(2015); 190 | console.log(myCar.age()); // 8 191 | ``` 192 | 193 | This works, but now `age` must be called as a function. Instead, we can use a [_getter_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get): 194 | 195 | ```js 196 | function createCar(make, year) { 197 | const currentYear = new Date().getFullYear(); 198 | 199 | function drive() { 200 | console.log(make + " goes VROOM"); 201 | } 202 | 203 | function setYear(newYear) { 204 | year = newYear; 205 | } 206 | 207 | return { 208 | make, 209 | drive, 210 | get age() { 211 | return currentYear - year; 212 | }, 213 | setYear 214 | }; 215 | } 216 | 217 | const myCar = createCar("Ford Fusion", 2018); 218 | console.log(myCar.age); // 5 219 | myCar.setYear(2015); 220 | console.log(myCar.age); // 8 221 | ``` 222 | 223 | Now, the interface is the same as if it were a normal object property, but it calculates its value on demand. 224 | 225 | Like any function, a getter can run any code. For example, you could `log` the number of times the property has been called: 226 | 227 | ```js 228 | function createCar(make, year) { 229 | const currentYear = new Date().getFullYear(); 230 | 231 | function drive() { 232 | console.log(make + " goes VROOM"); 233 | } 234 | 235 | function setYear(newYear) { 236 | year = newYear; 237 | } 238 | 239 | let accessed = 0; 240 | return { 241 | make, 242 | drive, 243 | get age() { 244 | console.log(++accessed); 245 | return currentYear - year; 246 | }, 247 | setYear 248 | }; 249 | } 250 | 251 | const myCar = createCar("Ford Fusion", 2018); 252 | myCar.age; // 1 253 | myCar.age; // 2 254 | myCar.age; // 3 255 | ``` 256 | 257 | You might not write many getters, but it's important to know that a property access can have "side effects", like the console log in this example. 258 | 259 | ## Destructuring 260 | 261 | In JavaScript, it is common to pass objects around as arguments: 262 | 263 | ```js 264 | function gameSetup(options) { 265 | initializeScreen(options.screenWidth, options.screenHeight); 266 | if (options.multiplayer) { 267 | startMultiplayerGame(); 268 | return; 269 | } 270 | startGame(); 271 | } 272 | ``` 273 | 274 | If you know that these three properties (`screenWidth`, `screenHeight`, and `multiplayer`) will be present on the `options` object, you can _destructure_ it to avoid repeating the name of the object: 275 | 276 | ```js 277 | function gameSetup(options, playersArray) { 278 | const { screenWidth, screenHeight, multiplayer } = options; 279 | initializeScreen(screenWidth, screenHeight); 280 | if (multiplayer) { 281 | console.log( 282 | "Starting a game with" + playersArray[0] + " and " + playersArray[1] 283 | ); 284 | startTwoPlayerGame(playersArray[0], playersArray[1]); 285 | return; 286 | } 287 | startGame(); 288 | } 289 | ``` 290 | 291 | You can even avoid giving the object a name in the first place: 292 | ```js 293 | function gameSetup({screenWidth, screenHight, multiplayer}, playersArray) { 294 | initializeScreen(screenWidth, screenHeight); 295 | if (multiplayer) { 296 | ... 297 | ``` 298 | 299 | You can also destructure arrays: 300 | 301 | ```js 302 | function gameSetup(options, playersArray) { 303 | const { screenWidth, screenHeight, multiplayer } = options; 304 | initializeScreen(screenWidth, screenHeight); 305 | if (multiplayer) { 306 | const [player1, player2] = playersArray; 307 | console.log("Starting a game with" + player1 + " and " + player2); 308 | startTwoPlayerGame(player1, player2); 309 | return; 310 | } 311 | startGame(); 312 | } 313 | ``` 314 | 315 | ## Template Literals 316 | 317 | You can simplify the console output in the previous example using template literals: 318 | 319 | ```js 320 | console.log(`Starting a game with ${player1} and ${player2}`); 321 | ``` 322 | 323 | Template literals also allow you to create multiline strings without having to manually insert a `\n` character: 324 | 325 | ```js 326 | const multiline = `spans two 327 | lines`; 328 | console.log(multiline); 329 | //spans two 330 | //lines 331 | ``` 332 | 333 | Some tools in the Solid ecosystem make use of [tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) which let you define a function that operates on a template literal and any included variables. This is used to easily bring special functionality to strings. For example, using Solid without JSX makes use of an `html` tagged template: 334 | 335 | ```js 336 | import html from "https://cdn.skypack.dev/solid-js/html"; 337 | 338 | function App() { 339 | const [count, setCount] = ... 340 | ... 341 | return html`
${count}
`; 342 | } 343 | 344 | ``` 345 | 346 | ## Arrow Functions 347 | 348 | There are many ways to declare a function in JavaScript. Here are most of them: 349 | 350 | 1. [Function declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#the_function_declaration_function_statement) 351 | 352 | ```js 353 | function areaOfCircle(radius) { 354 | return radius * Math.PI ** 2; 355 | } 356 | ``` 357 | 358 | 2. [Method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#defining_methods) 359 | 360 | ```js 361 | const utils = { 362 | areaOfCircle(radius) { 363 | return radius * Math.PI ** 2; 364 | }, 365 | }; 366 | ``` 367 | 368 | 3. [Function expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#the_function_expression_function_expression) 369 | 370 | ```js 371 | const areaOfCircle = function (radius) { 372 | return radius * Math.PI ** 2; 373 | }; 374 | ``` 375 | 376 | 4. [Arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 377 | 378 | ```js 379 | const areaOfCircle = (radius) => { 380 | return radius * Math.PI ** 2; 381 | }; 382 | 383 | // Or the shorthand if you only have one line of code in the function body: 384 | const areaOfCircle = (radius) => radius * Math.PI ** 2; 385 | ``` 386 | 387 | There are nuanced differences between these declarations, but they are irrelevant for day-to-day working with Solid (if you're curious, check out this [detailed article](https://dmitripavlutin.com/differences-between-arrow-and-regular-functions/)). 388 | 389 | If you're in doubt, use an arrow function (example #4). As long as you're not using the `this` keyword, there will be no difference between an arrow function and a function expression. 390 | 391 | 392 | ## Functions as Arguments 393 | 394 | You can accept a function as an argument to a function: 395 | 396 | ```js 397 | function callForEach(array, func) { 398 | for (let i = 0; i < array.length; i++) { 399 | const currentElement = array[i]; 400 | func(currentElement); 401 | } 402 | } 403 | 404 | const myArray = ["I", "love", "Solid"]; 405 | callForEach(myArray, console.log); 406 | // I 407 | // love 408 | // Solid 409 | ``` 410 | 411 | Many functions that are built in to JavaScript (and many that come with Solid) take a function argument. The above functionality can be found as `.forEach` on JavaScript arrays: 412 | 413 | ```js 414 | const myArray = ["I", "love", "Solid"]; 415 | myArray.forEach(console.log); 416 | // I 417 | // love 418 | // Solid 419 | ``` 420 | 421 | Another common example is the built-in `map` method. It takes as its argument a function that maps the current element of the array to a new result: 422 | 423 | ```js 424 | const myArray = ["I", "love", "Solid"]; 425 | const uppercase = myArray.map(element => element.toUpperCase()) // ["I", "LOVE", "SOLID"] 426 | ``` 427 | 428 | ## Declarative vs Imperative Code 429 | 430 | JavaScript array methods like `map` (and others like [`reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) and [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)) can replace loops that you might make with `for` or `while`. Those traditional loop techniques are "imperative". They state step-by-step how to accomplish something: 431 | 432 | ```js 433 | const myNumbers = [1, 2, 3, 4, 5, 11, 18, 65]; 434 | let oddNumbers = []; 435 | for (const number of myNumbers) { 436 | if (number % 2 !== 0) { 437 | oddNumbers.push(number); 438 | } 439 | } 440 | console.log(oddNumbers); // [ 1, 3, 5, 11, 65 ] 441 | ``` 442 | 443 | Using JavaScript array methods allow for more "declarative" code, where you state _what_ to do but not the full details of _how_. 444 | 445 | ```js 446 | const myNumbers = [1, 2, 3, 4, 5, 11, 18, 65]; 447 | const oddNumbers = myNumbers.filter((number) => number % 2 !== 0); 448 | console.log(oddNumbers); // [ 1, 3, 5, 11, 65 ] 449 | ``` 450 | 451 | The `filter` array method accomplished the same result in one line of code. It take a function and returns a new array that contains only the elements that return true when passed to the function. 452 | 453 | "Declarative" code makes use of abstractions to write streamlined code, focused more on _what_ to accomplish _how_. Using `filter` allows you to write less "boilerplate" looping code, and instead focus on the core functionality. The code is more descriptive, too. `for` loops can be used for all sorts of purposes, but whenever you see `filter` you know that the purpose of the looping is to ignore some items in an array. 454 | 455 | HTML is a great example of a _declarative_ way of writing: you state _what_ you want the structure to be, not _how_ it should be put together or rendered. Similarly, Solid makes it easier to write declarative code when working with interactive user interfaces. -------------------------------------------------------------------------------- /src/content/docs/tutorial/_components/FinishedBookshelf.tsx: -------------------------------------------------------------------------------- 1 | import { createResource, createSignal, For, Setter, Show } from "solid-js"; 2 | 3 | interface Book { 4 | title: string; 5 | author: string; 6 | } 7 | 8 | const initialBooks: Book[] = [ 9 | { title: "Code Complete", author: "Steve McConnell" }, 10 | { title: "The Hobbit", author: "J.R.R. Tolkien" }, 11 | { title: "Living a Feminist Life", author: "Sarah Ahmed" }, 12 | ]; 13 | 14 | interface BookshelfProps { 15 | name: string; 16 | } 17 | 18 | export function FinishedBookshelf(props: BookshelfProps) { 19 | const [books, setBooks] = createSignal(initialBooks); 20 | const [showForm, setShowForm] = createSignal(false); 21 | 22 | const toggleForm = () => setShowForm(!showForm()); 23 | 24 | return ( 25 |
26 |
27 |

{props.name}'s Bookshelf

28 | 29 |
30 |
31 | Add a book} 34 | > 35 | 36 | 37 | Finished adding books 38 | 39 | 40 |
41 |
42 | ); 43 | } 44 | 45 | interface BookListProps { 46 | books: Book[]; 47 | } 48 | 49 | export function BookList(props: BookListProps) { 50 | return ( 51 | <> 52 |

My books: {props.books.length}

53 |
    54 | 55 | {(book) => { 56 | return ( 57 |
  • 58 | {book.title} ({book.author}) 59 |
  • 60 | ); 61 | }} 62 |
    63 |
64 | 65 | ); 66 | } 67 | 68 | interface ResultItem { 69 | title: string; 70 | author_name: string[]; 71 | } 72 | 73 | interface Response { 74 | docs: ResultItem[]; 75 | } 76 | 77 | interface AddBookProps { 78 | setBooks: Setter; 79 | } 80 | 81 | async function searchBooks(query: string) { 82 | if (query.trim() === "") return []; 83 | const response = await fetch( 84 | `https://openlibrary.org/search.json?q=${encodeURI(query)}` 85 | ); 86 | 87 | const results = (await response.json()) as Response; 88 | const documents = results.docs; 89 | return documents.slice(0, 10).map(({ title, author_name }) => ({ 90 | title, 91 | author: author_name?.join(", "), 92 | })); 93 | } 94 | 95 | export function AddBook(props: AddBookProps) { 96 | const [input, setInput] = createSignal(""); 97 | const [query, setQuery] = createSignal(""); 98 | const [data] = createResource(query, searchBooks); 99 | return ( 100 | <> 101 |
102 |
103 | 104 | { 108 | setInput(e.currentTarget.value); 109 | }} 110 | /> 111 |
112 | 121 |
122 | Searching...}> 123 |
    124 | 125 | {(book) => ( 126 |
  • 127 | {book.title} by {book.author}{" "} 128 | 137 |
  • 138 | )} 139 |
    140 |
141 |
142 | 143 | ); 144 | } 145 | -------------------------------------------------------------------------------- /src/content/docs/tutorial/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Beginner Tutorial 3 | draft: false 4 | --- 5 | 6 | import Preferences from "~/components/Preferences/Preferences.tsx"; 7 | import CodeSample from "~/components/CodeSample/CodeSample.astro"; 8 | import Aside from "~/components/Aside.astro"; 9 | import FrameworkAside from "~/components/FrameworkAside.astro"; 10 | 11 | # Welcome 12 | 13 | Welcome to the Solid tutorial! In this tutorial, you'll learn Solid by doing: building an app and exploring Solid along the way. 14 | 15 | You'll build a small "bookshelf" app that lets you search a library database and add books to a list. 16 | 17 | {/* */} 18 | 19 | 20 | ## What is Solid? 21 | 22 | Solid is a JavaScript framework for making interactive web applications. With Solid, you can use your existing HTML and JavaScript knowledge to build components that can be reused throughout your app. Solid provides the tools to enhance your components with reactivity: declarative JavaScript code that links the user interface with the data that it uses and creates. 23 | 24 | ## Prerequisites 25 | 26 | Before digging into Solid, we recommend you have some experience building websites and a working understanding of HTML, CSS, and JavaScript. 27 | 28 | 32 | 33 | ## Tell us a little about yourself 34 | 35 | Before we get started, we'd like to know a little about you! By telling us your language preference and if you're coming from another framework, we'll be able to tailor this tutorial to match your experience as best we can. 36 | 37 | 38 | 39 | 40 | As you go through the tutorial, we'll give you helpful hints in informational 41 | boxes just like this one that relate Solid with React principles and 42 | practices. 43 | 44 | 45 | 46 | As you go through the tutorial, we'll give you helpful hints in informational 47 | boxes just like this one that relate Solid with Svelte principles and 48 | practices. 49 | 50 | 51 | 52 | As you go through the tutorial, we'll give you helpful hints in informational 53 | boxes just like this one that relate Solid with Vue principles and practices. 54 | 55 | 56 | 57 | As you go through the tutorial, we'll give you helpful hints in informational 58 | boxes just like this one that relate Solid with Angular principles and 59 | practices. 60 | 61 | 62 | ## Need help? 63 | 64 | If you ever need help along the way, [don't hesitate to reach out to the Solid team on Discord!](https://discord.com/invite/solidjs) Chances are that if something in this tutorial is unclear to you, then it's likely unclear to others as well. By reaching out to us, you'll be helping improve the learning experience of others as well. -------------------------------------------------------------------------------- /src/content/docs/tutorial/installing-solid.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installing Solid 3 | --- 4 | import CodeSample from "~/components/CodeSample/CodeSample.astro" 5 | import IfTS from "~/components/Preferences/IfTS.astro" 6 | 7 | # Installing Solid 8 | 9 | You can work with Solid using a browser-based editor, or install it locally on your computer. 10 | 11 | ## In the browser 12 | 13 | The easiest way to get started with Solid is to choose a browser-based option. 14 | 15 | - **StackBlitz Starters**: Stackblitz is a web-based development environment. This 16 | [TypeScript starter](https://stackblitz.com/github/solidjs/templates/tree/master/ts) 17 | will set up a full project environment for you, and everything will run directly in your browser. 18 | 19 | ## In your local environment 20 | 21 | To run Solid on your own computer, make sure you have [Node.js](https://nodejs.org) installed. 22 | Then, run one of these commands, which pull from our [Vite templates](https://github.com/solidjs/templates). 23 | 24 | In the TypeScript templates, code files will end in `.ts` and `.tsx` instead of `.js` and `.jsx`. 25 | 26 | 27 |

28 | ```bash 29 | npx degit solidjs/templates/js my-app 30 | cd my-app 31 | npm install # or yarn or pnpm 32 | npm run dev # or yarn or pnpm 33 | ``` 34 |

35 |

36 | ```bash 37 | npx degit solidjs/templates/ts my-app 38 | cd my-app 39 | npm install # or yarn or pnpm 40 | npm run dev # or yarn or pnpm 41 | ``` 42 |

43 |
44 | 45 | ## Understanding the Project Structure 46 | 47 | These templates set up a project with many files, but it isn't important to understand them all now. We'll guide you through files as you need them. 48 | 49 | 50 |

51 | ``` 52 | my-app 53 | ├── README.md 54 | ├── index.html 55 | ├── jsconfig.json 56 | ├── package.json 57 | ├── pnpm-lock.yaml 58 | ├── src 59 | │   ├── App.jsx 60 | │   ├── App.module.css 61 | │   ├── assets 62 | │   │   └── favicon.ico 63 | │   ├── index.css 64 | │   ├── index.jsx 65 | │   └── logo.svg 66 | └── vite.config.js 67 | ``` 68 |

69 |

70 | ``` 71 | my-app 72 | ├── README.md 73 | ├── index.html 74 | ├── package.json 75 | ├── pnpm-lock.yaml 76 | ├── src 77 | │   ├── App.module.css 78 | │   ├── App.tsx 79 | │   ├── assets 80 | │   │   └── favicon.ico 81 | │   ├── index.css 82 | │   ├── index.tsx 83 | │   └── logo.svg 84 | ├── tsconfig.json 85 | └── vite.config.ts 86 | ``` 87 |

88 |
89 | 90 | For now, here are the basics: 91 | 92 | - We're using [Vite](https://vitejs.dev/) to run our code. Vite is a tool that allows us to import one file from another and use plugins to improve the development experience. When we're ready to deploy our app, Vite bundles our code. 93 | The vite.config.ts file configures the Vite project. 94 | - Everything we'll be doing in this tutorial takes place in the `src` folder. 95 | - The starting point for our site is `index.html`. This provides a basic HTML skeleton, and links to src/index.tsx. 96 | - src/index.tsx is the starting point for our Solid app. 97 | This in turn imports src/App.tsx, which is our first component. 98 | 99 | We'll discuss components next. 100 | -------------------------------------------------------------------------------- /src/control-flow.d.ts: -------------------------------------------------------------------------------- 1 | declare const For: typeof import('solid-js/web').For; 2 | declare const Index: typeof import('solid-js/web').Index; 3 | declare const Show: typeof import('solid-js/web').Show; 4 | declare const ErrorBoundary: typeof import('solid-js/web').ErrorBoundary; 5 | declare const Suspense: typeof import('solid-js/web').Suspense; 6 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /src/layouts/BaseLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { MarkdownHeading } from "astro"; 3 | import type { CollectionEntry } from "astro:content"; 4 | import LeftSidebar from "../components/LeftSidebar.astro"; 5 | import RightSidebar from "../components/RightSidebar.astro"; 6 | import Header from "../components/Header/Header.astro"; 7 | 8 | interface Props { 9 | content: CollectionEntry<"docs">["data"]; 10 | headings: MarkdownHeading[]; 11 | } 12 | 13 | const { content, headings } = Astro.props; 14 | 15 | const currentPage = Astro.url.pathname; 16 | --- 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {content.title} 26 | 57 | 58 | 59 |
60 |
61 | 64 |
65 |
66 | 67 |
68 |
69 | 72 |
73 | 74 | 75 | 76 | 150 | -------------------------------------------------------------------------------- /src/pages/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { CollectionEntry } from "astro:content"; 3 | import { allPages } from "../content"; 4 | import BaseLayout from "../layouts/BaseLayout.astro"; 5 | 6 | export function getStaticPaths() { 7 | return allPages.map((page) => { 8 | const slug = page.slug; 9 | 10 | return { params: { slug }, props: page }; 11 | }); 12 | } 13 | 14 | const { data, render } = Astro.props as CollectionEntry<"docs">; 15 | const { Content, headings } = await render(); 16 | --- 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/sidebar.ts: -------------------------------------------------------------------------------- 1 | export type SidebarEntry = { text: string; slug: string }; 2 | type Sidebar = Record>; 3 | 4 | type GeneratedSidebar = { 5 | header: string; 6 | depth: number; 7 | children: SidebarEntry[]; 8 | }[]; 9 | 10 | export const sidebar: Sidebar = { 11 | "Start Here": [ 12 | { text: "Welcome", slug: "start-here" }, 13 | { text: "JavaScript for Solid", slug: "start-here/js-for-solid" }, 14 | ], 15 | Tutorials: { 16 | "Getting Started with Solid": [ 17 | { text: "Introduction", slug: "tutorial" }, 18 | { text: "Installing Solid", slug: "tutorial/installing-solid" }, 19 | ], 20 | }, 21 | "Core Concepts": [], 22 | "API Reference": { 23 | Router: [ 24 | { text: "A", slug: "api/a" }, 25 | { text: "B", slug: "api/b" }, 26 | ], 27 | }, 28 | }; 29 | 30 | // Converts the sidebar object into a easier format for component-usage. 31 | export function getSidebarEntries() { 32 | const entries: GeneratedSidebar = []; 33 | 34 | Object.entries(sidebar).map(([header, items]) => { 35 | if (Array.isArray(items)) { 36 | entries.push({ 37 | header: header, 38 | depth: 1, 39 | children: items, 40 | }); 41 | } else { 42 | entries.push({ 43 | header: header, 44 | depth: 1, 45 | children: [], 46 | }); 47 | 48 | Object.entries(items).map(([header, entry]) => { 49 | entries.push({ 50 | header: header, 51 | depth: 2, 52 | children: entry, 53 | }); 54 | }); 55 | } 56 | }); 57 | 58 | return entries; 59 | } 60 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "ignoreDeprecations": "5.0", 5 | "jsx": "preserve", 6 | "jsxImportSource": "solid-js", 7 | "allowImportingTsExtensions": true, 8 | "baseUrl": ".", 9 | "paths": { 10 | "~/*": ["src/*"] 11 | } 12 | } 13 | } 14 | --------------------------------------------------------------------------------