docs: Add React reducer code snippets and config updates
This commit is contained in:
parent
68344817ae
commit
02f81adaf3
@ -1,46 +1,19 @@
|
|||||||
import { Highlight, Prism } from "prism-react-renderer";
|
|
||||||
import { prismThemes } from "@/data/themes/prism";
|
|
||||||
import { useTheme } from "@/hooks/useTheme";
|
|
||||||
import { Fragment, useMemo } from "react";
|
|
||||||
|
|
||||||
import { clientOnly } from "vike-react/clientOnly";
|
import { clientOnly } from "vike-react/clientOnly";
|
||||||
|
import { SSRSnippet } from "./SSRSnippet";
|
||||||
|
|
||||||
const CSRSnippet = clientOnly(() => import("./CSRSnippet"));
|
const CSRSnippet = clientOnly(() => import("./CSRSnippet"));
|
||||||
|
|
||||||
function SSRFence({ children, language }: { children: string; language: string }) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const prismTheme = useMemo(() => {
|
|
||||||
return prismThemes[theme];
|
|
||||||
}, [theme]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}>
|
|
||||||
{({ className, style, tokens, getTokenProps }) => (
|
|
||||||
<pre className={className} style={style}>
|
|
||||||
<code>
|
|
||||||
{tokens.map((line, lineIndex) => (
|
|
||||||
<Fragment key={lineIndex}>
|
|
||||||
{line
|
|
||||||
.filter((token) => !token.empty)
|
|
||||||
.map((token, tokenIndex) => (
|
|
||||||
<span key={tokenIndex} {...getTokenProps({ token })} />
|
|
||||||
))}
|
|
||||||
{"\n"}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</code>
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</Highlight>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Fence({ children, language }: { children: string; language: string }) {
|
export function Fence({ children, language }: { children: string; language: string }) {
|
||||||
|
const props = {
|
||||||
|
language,
|
||||||
|
label: undefined,
|
||||||
|
showLineNumbers: false,
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<CSRSnippet language={language} fallback={<SSRFence language={language} children={children} />}>
|
<CSRSnippet {...props} fallback={<SSRSnippet {...props} />} />
|
||||||
{children}
|
|
||||||
</CSRSnippet>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
58
app/components/syntax/SSRSnippet.tsx
Normal file
58
app/components/syntax/SSRSnippet.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Highlight, Prism } from "prism-react-renderer";
|
||||||
|
import { prismThemes } from "@/data/themes/prism";
|
||||||
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
import { Fragment, useMemo } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
export function SSRSnippet({
|
||||||
|
children,
|
||||||
|
language,
|
||||||
|
label,
|
||||||
|
showLineNumbers = false,
|
||||||
|
}: {
|
||||||
|
children: string;
|
||||||
|
language: string;
|
||||||
|
label?: string;
|
||||||
|
showLineNumbers?: boolean;
|
||||||
|
}) {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const prismTheme = useMemo(() => {
|
||||||
|
return prismThemes[theme];
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}>
|
||||||
|
{({ className, style, tokens, getTokenProps }) => (
|
||||||
|
<div className="relative w-full">
|
||||||
|
{label && (
|
||||||
|
<div className="absolute px-4 py-1 left-0 text-sm text-gray-700 dark:text-gray-200 italic w-full bg-gray-200 dark:bg-gray-700 rounded-t-xl">
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<pre className={clsx(className, { "pt-11": !!label })} style={style}>
|
||||||
|
<code>
|
||||||
|
{tokens.map((line, lineIndex) => (
|
||||||
|
<Fragment key={lineIndex}>
|
||||||
|
{showLineNumbers && (
|
||||||
|
<span
|
||||||
|
className="text-gray-400 dark:text-gray-500 text-right font-mono w-8 inline-block pr-4"
|
||||||
|
style={{ userSelect: "none" }}
|
||||||
|
>
|
||||||
|
{lineIndex + 1}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{line
|
||||||
|
.filter((token) => !token.empty)
|
||||||
|
.map((token, tokenIndex) => (
|
||||||
|
<span key={tokenIndex} {...getTokenProps({ token })} />
|
||||||
|
))}
|
||||||
|
{"\n"}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Highlight>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,57 +1,11 @@
|
|||||||
import type { Data } from "@/pages/docs/+data";
|
import type { Data } from "@/pages/docs/+data";
|
||||||
|
|
||||||
import { snippetsService } from "@/services/SnippetsService";
|
|
||||||
import { Highlight, Prism } from "prism-react-renderer";
|
|
||||||
import { clientOnly } from "vike-react/clientOnly";
|
import { clientOnly } from "vike-react/clientOnly";
|
||||||
import { prismThemes } from "@/data/themes/prism";
|
|
||||||
import { useData } from "vike-react/useData";
|
import { useData } from "vike-react/useData";
|
||||||
import { useTheme } from "@/hooks/useTheme";
|
import { SSRSnippet } from "./SSRSnippet";
|
||||||
import { Fragment, useMemo } from "react";
|
|
||||||
|
|
||||||
const CSRSnippet = clientOnly(() => import("./CSRSnippet"));
|
const CSRSnippet = clientOnly(() => import("./CSRSnippet"));
|
||||||
|
|
||||||
function SSRSnippet({
|
|
||||||
language,
|
|
||||||
children,
|
|
||||||
label,
|
|
||||||
showLineNumbers = false,
|
|
||||||
}: {
|
|
||||||
language: string;
|
|
||||||
children: string;
|
|
||||||
label?: string;
|
|
||||||
showLineNumbers?: boolean;
|
|
||||||
}) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const prismTheme = useMemo(() => {
|
|
||||||
return prismThemes[theme];
|
|
||||||
}, [theme]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{label && <div className="text-sm text-gray-500 dark:text-gray-400 mb-2">{label}</div>}
|
|
||||||
<Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}>
|
|
||||||
{({ className, style, tokens, getTokenProps }) => (
|
|
||||||
<pre className={className} style={style}>
|
|
||||||
<code>
|
|
||||||
{tokens.map((line, lineIndex) => (
|
|
||||||
<Fragment key={lineIndex}>
|
|
||||||
{line
|
|
||||||
.filter((token) => !token.empty)
|
|
||||||
.map((token, tokenIndex) => (
|
|
||||||
<span key={tokenIndex} {...getTokenProps({ token })} />
|
|
||||||
))}
|
|
||||||
{"\n"}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</code>
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</Highlight>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Snippet({
|
export function Snippet({
|
||||||
path,
|
path,
|
||||||
language,
|
language,
|
||||||
|
|||||||
@ -47,19 +47,13 @@ Comme expliqué plus tôt, un reducer est une fonction qui prend en paramètre u
|
|||||||
Parlons dans un premier temps de la signature d'un reducer :
|
Parlons dans un premier temps de la signature d'un reducer :
|
||||||
|
|
||||||
{% tabs defaultSelectedTab="jsx" %}
|
{% tabs defaultSelectedTab="jsx" %}
|
||||||
|
|
||||||
{% tab value="jsx" label="JSX" %}
|
{% tab value="jsx" label="JSX" %}
|
||||||
|
{% snippet path="react/reducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%}
|
||||||
{% snippet path="data/docs/react/usereducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%}
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
{% tab value="tsx" label="TSX" %}
|
{% tab value="tsx" label="TSX" %}
|
||||||
|
{% snippet path="react/reducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%}
|
||||||
{% snippet path="data/docs/react/usereducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%}
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
{% /tabs %}
|
{% /tabs %}
|
||||||
|
|
||||||
Comme tu peux le voir, on récupère bien deux paramètres : `state` et `action`.
|
Comme tu peux le voir, on récupère bien deux paramètres : `state` et `action`.
|
||||||
@ -82,7 +76,7 @@ En déversant le contenu de l'état actuel, on s'assure de ne pas perdre ces pro
|
|||||||
|
|
||||||
Par exemple :
|
Par exemple :
|
||||||
|
|
||||||
{% snippet path="data/docs/react/usereducer/reducer-why-spread-operator.jsx" language="jsx" showLineNumbers=true /%}
|
{% snippet path="react/reducer/reducer-why-spread-operator.jsx" language="jsx" showLineNumbers=true /%}
|
||||||
|
|
||||||
On perdrait ici la propriété `message` si on ne la déversait pas dans le nouvel état.
|
On perdrait ici la propriété `message` si on ne la déversait pas dans le nouvel état.
|
||||||
|
|
||||||
@ -103,20 +97,13 @@ Ensuite, on va définir notre état initial :
|
|||||||
{% tabs defaultSelectedTab="js" %}
|
{% tabs defaultSelectedTab="js" %}
|
||||||
{% tab value="js" label="JavaScript" %}
|
{% tab value="js" label="JavaScript" %}
|
||||||
|
|
||||||
```js
|
{% snippet path="react/reducer/reducer-initial-state.js" language="js" /%}
|
||||||
const initialState = { count: 0 };
|
|
||||||
```
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
{% tab value="ts" label="TypeScript" %}
|
{% tab value="ts" label="TypeScript" %}
|
||||||
|
|
||||||
```ts
|
{% snippet path="react/reducer/reducer-initial-state.ts" language="ts" /%}
|
||||||
type State = {
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
const initialState: State = { count: 0 };
|
|
||||||
```
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
@ -128,52 +115,13 @@ On peut maintenant définir notre reducer :
|
|||||||
|
|
||||||
{% tab value="js" label="JavaScript" %}
|
{% tab value="js" label="JavaScript" %}
|
||||||
|
|
||||||
```js
|
{% snippet path="react/reducer/reducer.js" language="js" showLineNumbers=true /%}
|
||||||
const reducer = (state, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case "INCREMENT":
|
|
||||||
return { ...state, count: state.count + 1 };
|
|
||||||
case "DECREMENT":
|
|
||||||
return { ...state, count: state.count - 1 };
|
|
||||||
case "RESET":
|
|
||||||
return { ...state, count: 0 };
|
|
||||||
case "SET":
|
|
||||||
return { ...state, count: action.payload };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
{% tab value="ts" label="TypeScript" %}
|
{% tab value="ts" label="TypeScript" %}
|
||||||
|
|
||||||
```ts
|
{% snippet path="react/reducer/reducer.ts" language="ts" showLineNumbers=true /%}
|
||||||
type State = {
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Action = {
|
|
||||||
type: "INCREMENT" | "DECREMENT" | "RESET" | "SET";
|
|
||||||
payload?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (state: State, action: Action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case "INCREMENT":
|
|
||||||
return { ...state, count: state.count + 1 };
|
|
||||||
case "DECREMENT":
|
|
||||||
return { ...state, count: state.count - 1 };
|
|
||||||
case "RESET":
|
|
||||||
return { ...state, count: 0 };
|
|
||||||
case "SET":
|
|
||||||
return { ...state, count: action.payload! };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
@ -196,17 +144,13 @@ Enfin, on peut utiliser le hook useReducer dans notre composant :
|
|||||||
|
|
||||||
{% tab value="js" label="JavaScript" %}
|
{% tab value="js" label="JavaScript" %}
|
||||||
|
|
||||||
```js
|
{% snippet path="react/reducer/reducer-hook.js" language="js" /%}
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
|
||||||
```
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
{% tab value="ts" label="TypeScript" %}
|
{% tab value="ts" label="TypeScript" %}
|
||||||
|
|
||||||
```ts
|
{% snippet path="react/reducer/reducer-hook.ts" language="ts" /%}
|
||||||
const [state, dispatch] = useReducer<State, Action>(reducer, initialState);
|
|
||||||
```
|
|
||||||
|
|
||||||
{% /tab %}
|
{% /tab %}
|
||||||
|
|
||||||
|
|||||||
1
app/data/snippets/react/reducer/reducer-hook.js
Normal file
1
app/data/snippets/react/reducer/reducer-hook.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
1
app/data/snippets/react/reducer/reducer-hook.ts
Normal file
1
app/data/snippets/react/reducer/reducer-hook.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
const [state, dispatch] = useReducer<State, Action>(reducer, initialState);
|
||||||
1
app/data/snippets/react/reducer/reducer-initial-state.js
Normal file
1
app/data/snippets/react/reducer/reducer-initial-state.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
const initialState = { count: 0 };
|
||||||
4
app/data/snippets/react/reducer/reducer-initial-state.ts
Normal file
4
app/data/snippets/react/reducer/reducer-initial-state.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
type State = {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
const initialState: State = { count: 0 };
|
||||||
14
app/data/snippets/react/reducer/reducer.js
Normal file
14
app/data/snippets/react/reducer/reducer.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const reducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "INCREMENT":
|
||||||
|
return { ...state, count: state.count + 1 };
|
||||||
|
case "DECREMENT":
|
||||||
|
return { ...state, count: state.count - 1 };
|
||||||
|
case "RESET":
|
||||||
|
return { ...state, count: 0 };
|
||||||
|
case "SET":
|
||||||
|
return { ...state, count: action.payload };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
23
app/data/snippets/react/reducer/reducer.ts
Normal file
23
app/data/snippets/react/reducer/reducer.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
type State = {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Action = {
|
||||||
|
type: "INCREMENT" | "DECREMENT" | "RESET" | "SET";
|
||||||
|
payload?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = (state: State, action: Action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "INCREMENT":
|
||||||
|
return { ...state, count: state.count + 1 };
|
||||||
|
case "DECREMENT":
|
||||||
|
return { ...state, count: state.count - 1 };
|
||||||
|
case "RESET":
|
||||||
|
return { ...state, count: 0 };
|
||||||
|
case "SET":
|
||||||
|
return { ...state, count: action.payload! };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -10,6 +10,7 @@ export default tseslint.config(
|
|||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
"dist/*",
|
"dist/*",
|
||||||
|
"data/snippets/*",
|
||||||
// Temporary compiled files
|
// Temporary compiled files
|
||||||
"**/*.ts.build-*.mjs",
|
"**/*.ts.build-*.mjs",
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ type DocData = { title: string; description: string; content: string; sections:
|
|||||||
type DocExtension = "mdx" | "md";
|
type DocExtension = "mdx" | "md";
|
||||||
|
|
||||||
class DocsService {
|
class DocsService {
|
||||||
|
private static readonly SNIPPETS_PATH = path.resolve(path.join(__dirname, "data", "snippets"));
|
||||||
private static readonly DOCS_PATH = path.resolve(path.join(__dirname, "data"));
|
private static readonly DOCS_PATH = path.resolve(path.join(__dirname, "data"));
|
||||||
private static readonly DOCS_EXTS: DocExtension[] = ["mdx", "md"]; // Order matters
|
private static readonly DOCS_EXTS: DocExtension[] = ["mdx", "md"]; // Order matters
|
||||||
private static instance: DocsService;
|
private static instance: DocsService;
|
||||||
@ -83,18 +84,23 @@ class DocsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fetchSnippets(content: string) {
|
public fetchSnippets(content: string) {
|
||||||
const identifierResults = snippetsService.identifyNewSnippets(content);
|
try {
|
||||||
if (!identifierResults) return;
|
const identifierResults = snippetsService.identifyNewSnippets(content);
|
||||||
|
if (!identifierResults) return [];
|
||||||
|
|
||||||
const [snippetsToFetch, allSnippets] = identifierResults;
|
const [snippetsToFetch, allSnippets] = identifierResults;
|
||||||
|
|
||||||
for (const snippet of snippetsToFetch) {
|
for (const snippet of snippetsToFetch) {
|
||||||
const absolutePath = path.resolve(__dirname, snippet);
|
const absolutePath = path.resolve(DocsService.SNIPPETS_PATH, snippet);
|
||||||
const content = fs.readFileSync(absolutePath, "utf-8");
|
const content = fs.readFileSync(absolutePath, "utf-8");
|
||||||
snippetsService.setToCache(snippet, content);
|
snippetsService.setToCache(snippet, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allSnippets || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching snippets:", error);
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return allSnippets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchDocs() {
|
public async fetchDocs() {
|
||||||
@ -109,15 +115,13 @@ class DocsService {
|
|||||||
.replace(`.${extension}`, "")
|
.replace(`.${extension}`, "")
|
||||||
.replace(/\/$/g, "");
|
.replace(/\/$/g, "");
|
||||||
|
|
||||||
const allSnippets = this.fetchSnippets(content);
|
|
||||||
|
|
||||||
const ast = Markdoc.parse(content);
|
const ast = Markdoc.parse(content);
|
||||||
const title = ast.attributes?.frontmatter?.match(/^title:\s*(.*?)\s*$/m)?.[1];
|
const title = ast.attributes?.frontmatter?.match(/^title:\s*(.*?)\s*$/m)?.[1];
|
||||||
const description = ast.attributes?.frontmatter?.match(/^description:\s*(.*?)\s*$/m)?.[1]?.replaceAll('"', "");
|
const description = ast.attributes?.frontmatter?.match(/^description:\s*(.*?)\s*$/m)?.[1]?.replaceAll('"', "");
|
||||||
const sections: DocSection[] = [[title, null, []]];
|
const sections: DocSection[] = [[title, null, []]];
|
||||||
|
|
||||||
this.extractSections(ast, sections);
|
this.extractSections(ast, sections);
|
||||||
this.setToCache(key, { title, description, content, sections, snippets: allSnippets || [] });
|
this.setToCache(key, { title, description, content, sections, snippets: this.fetchSnippets(content) });
|
||||||
|
|
||||||
return { key, sections };
|
return { key, sections };
|
||||||
}),
|
}),
|
||||||
@ -152,6 +156,7 @@ class DocsService {
|
|||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Error fetching docs:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,5 +20,5 @@
|
|||||||
"@syntax/*": ["./components/syntax/*"]
|
"@syntax/*": ["./components/syntax/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["dist"]
|
"exclude": ["dist", "data/snippets"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user