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 { SSRSnippet } from "./SSRSnippet";
|
||||
|
||||
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 }) {
|
||||
const props = {
|
||||
language,
|
||||
label: undefined,
|
||||
showLineNumbers: false,
|
||||
children,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<CSRSnippet language={language} fallback={<SSRFence language={language} children={children} />}>
|
||||
{children}
|
||||
</CSRSnippet>
|
||||
<CSRSnippet {...props} fallback={<SSRSnippet {...props} />} />
|
||||
</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 { snippetsService } from "@/services/SnippetsService";
|
||||
import { Highlight, Prism } from "prism-react-renderer";
|
||||
import { clientOnly } from "vike-react/clientOnly";
|
||||
import { prismThemes } from "@/data/themes/prism";
|
||||
import { useData } from "vike-react/useData";
|
||||
import { useTheme } from "@/hooks/useTheme";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { SSRSnippet } from "./SSRSnippet";
|
||||
|
||||
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({
|
||||
path,
|
||||
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 :
|
||||
|
||||
{% tabs defaultSelectedTab="jsx" %}
|
||||
|
||||
{% tab value="jsx" label="JSX" %}
|
||||
|
||||
{% snippet path="data/docs/react/usereducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%}
|
||||
|
||||
{% snippet path="react/reducer/reducer-example.jsx" language="jsx" showLineNumbers=true /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="tsx" label="TSX" %}
|
||||
|
||||
{% snippet path="data/docs/react/usereducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%}
|
||||
|
||||
{% snippet path="react/reducer/reducer-example.tsx" language="tsx" showLineNumbers=true /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
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 :
|
||||
|
||||
{% 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.
|
||||
|
||||
@ -103,20 +97,13 @@ Ensuite, on va définir notre état initial :
|
||||
{% tabs defaultSelectedTab="js" %}
|
||||
{% tab value="js" label="JavaScript" %}
|
||||
|
||||
```js
|
||||
const initialState = { count: 0 };
|
||||
```
|
||||
{% snippet path="react/reducer/reducer-initial-state.js" language="js" /%}
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="ts" label="TypeScript" %}
|
||||
|
||||
```ts
|
||||
type State = {
|
||||
count: number;
|
||||
};
|
||||
const initialState: State = { count: 0 };
|
||||
```
|
||||
{% snippet path="react/reducer/reducer-initial-state.ts" language="ts" /%}
|
||||
|
||||
{% /tab %}
|
||||
|
||||
@ -128,52 +115,13 @@ On peut maintenant définir notre reducer :
|
||||
|
||||
{% tab value="js" label="JavaScript" %}
|
||||
|
||||
```js
|
||||
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;
|
||||
}
|
||||
};
|
||||
```
|
||||
{% snippet path="react/reducer/reducer.js" language="js" showLineNumbers=true /%}
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="ts" label="TypeScript" %}
|
||||
|
||||
```ts
|
||||
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;
|
||||
}
|
||||
};
|
||||
```
|
||||
{% snippet path="react/reducer/reducer.ts" language="ts" showLineNumbers=true /%}
|
||||
|
||||
{% /tab %}
|
||||
|
||||
@ -196,17 +144,13 @@ Enfin, on peut utiliser le hook useReducer dans notre composant :
|
||||
|
||||
{% tab value="js" label="JavaScript" %}
|
||||
|
||||
```js
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
```
|
||||
{% snippet path="react/reducer/reducer-hook.js" language="js" /%}
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab value="ts" label="TypeScript" %}
|
||||
|
||||
```ts
|
||||
const [state, dispatch] = useReducer<State, Action>(reducer, initialState);
|
||||
```
|
||||
{% snippet path="react/reducer/reducer-hook.ts" language="ts" /%}
|
||||
|
||||
{% /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: [
|
||||
"dist/*",
|
||||
"data/snippets/*",
|
||||
// Temporary compiled files
|
||||
"**/*.ts.build-*.mjs",
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ type DocData = { title: string; description: string; content: string; sections:
|
||||
type DocExtension = "mdx" | "md";
|
||||
|
||||
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_EXTS: DocExtension[] = ["mdx", "md"]; // Order matters
|
||||
private static instance: DocsService;
|
||||
@ -83,18 +84,23 @@ class DocsService {
|
||||
}
|
||||
|
||||
public fetchSnippets(content: string) {
|
||||
try {
|
||||
const identifierResults = snippetsService.identifyNewSnippets(content);
|
||||
if (!identifierResults) return;
|
||||
if (!identifierResults) return [];
|
||||
|
||||
const [snippetsToFetch, allSnippets] = identifierResults;
|
||||
|
||||
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");
|
||||
snippetsService.setToCache(snippet, content);
|
||||
}
|
||||
|
||||
return allSnippets;
|
||||
return allSnippets || [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching snippets:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async fetchDocs() {
|
||||
@ -109,15 +115,13 @@ class DocsService {
|
||||
.replace(`.${extension}`, "")
|
||||
.replace(/\/$/g, "");
|
||||
|
||||
const allSnippets = this.fetchSnippets(content);
|
||||
|
||||
const ast = Markdoc.parse(content);
|
||||
const title = ast.attributes?.frontmatter?.match(/^title:\s*(.*?)\s*$/m)?.[1];
|
||||
const description = ast.attributes?.frontmatter?.match(/^description:\s*(.*?)\s*$/m)?.[1]?.replaceAll('"', "");
|
||||
const sections: DocSection[] = [[title, null, []]];
|
||||
|
||||
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 };
|
||||
}),
|
||||
@ -152,6 +156,7 @@ class DocsService {
|
||||
|
||||
return doc;
|
||||
} catch (error) {
|
||||
console.error("Error fetching docs:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,5 +20,5 @@
|
||||
"@syntax/*": ["./components/syntax/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["dist"]
|
||||
"exclude": ["dist", "data/snippets"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user