Compare commits
3 Commits
08c5ecfb6a
...
602669a9c0
| Author | SHA1 | Date | |
|---|---|---|---|
| 602669a9c0 | |||
| 8418ca4a74 | |||
| 6a5ade483b |
@ -42,6 +42,6 @@ export function Button(props: ButtonProps) {
|
|||||||
href={props.href}
|
href={props.href}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<button class={className} {...(props as JSX.IntrinsicElements["button"])} />
|
<button {...(props as JSX.IntrinsicElements["button"])} class={className} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,319 +1,24 @@
|
|||||||
import type { ComponentProps, ParentComponent } from "solid-js";
|
import type { ComponentProps, ParentComponent } from "solid-js";
|
||||||
|
|
||||||
import { createEffect, createMemo, mergeProps, on, splitProps } from "solid-js";
|
import {
|
||||||
|
createEffect,
|
||||||
|
createMemo,
|
||||||
|
For,
|
||||||
|
mergeProps,
|
||||||
|
on,
|
||||||
|
splitProps,
|
||||||
|
} from "solid-js";
|
||||||
|
import { clipboard } from "solid-heroicons/solid";
|
||||||
|
import { Icon } from "solid-heroicons";
|
||||||
import * as Prismjs from "prismjs";
|
import * as Prismjs from "prismjs";
|
||||||
|
import toast from "solid-toast";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://prismjs.com/#supported-languages
|
|
||||||
*/
|
|
||||||
export const Language = {
|
|
||||||
JAVASCRIPT: "javascript",
|
|
||||||
HTML: "html",
|
|
||||||
CSS: "css",
|
|
||||||
MARKUP: "markup",
|
|
||||||
XML: "xml",
|
|
||||||
SVG: "svg",
|
|
||||||
ABAP: "abap",
|
|
||||||
ABNF: "abnf",
|
|
||||||
ACTION_SCRIPT: "actionscript",
|
|
||||||
ADA: "ada",
|
|
||||||
AGDA: "agda",
|
|
||||||
AL: "al",
|
|
||||||
ANTLR4: "antlr4",
|
|
||||||
G4: "g4",
|
|
||||||
APACHE_CONFIGURATION: "apacheconf",
|
|
||||||
APEX: "apex",
|
|
||||||
APL: "apl",
|
|
||||||
APPLESCRIPT: "applescript",
|
|
||||||
AQL: "aql",
|
|
||||||
ARDUINO: "arduino",
|
|
||||||
ARFF: "arff",
|
|
||||||
ARM_ASSEMBLY: "armasm",
|
|
||||||
ARTURO: "arturo",
|
|
||||||
ASCIIDOC: "asciidoc",
|
|
||||||
ASP_NET: "aspnet",
|
|
||||||
ASSEMBLY_6502: "asm6502",
|
|
||||||
ASSEMBLY_ATMEL_AVR: "asmatmel",
|
|
||||||
AUTO_HOTKEY: "autohotkey",
|
|
||||||
AUTO_IT: "autoit",
|
|
||||||
AVI_SYNTH: "avisynth",
|
|
||||||
AVRO_IDL: "avro-idl",
|
|
||||||
AWK: "awk",
|
|
||||||
BASH: "bash",
|
|
||||||
BASIC: "basic",
|
|
||||||
BATCH: "batch",
|
|
||||||
BBCODE: "bbcode",
|
|
||||||
BBJ: "bbj",
|
|
||||||
BICEP: "bicep",
|
|
||||||
BIRB: "birb",
|
|
||||||
BISON: "bison",
|
|
||||||
BNF: "bnf",
|
|
||||||
BQN: "bqn",
|
|
||||||
BRAINFUCK: "brainfuck",
|
|
||||||
BRIGHT_SCRIPT: "brightscript",
|
|
||||||
BRO: "bro",
|
|
||||||
BSL: "bsl",
|
|
||||||
C: "c",
|
|
||||||
CSHARP: "csharp",
|
|
||||||
CPP: "cpp",
|
|
||||||
CF_SCRIPT: "cfscript",
|
|
||||||
CHAI_SCRIPT: "chaiscript",
|
|
||||||
CIL: "cil",
|
|
||||||
CILK_C: "cilkc",
|
|
||||||
CILK_CPP: "cilkcpp",
|
|
||||||
CLOJURE: "clojure",
|
|
||||||
CMAKE: "cmake",
|
|
||||||
COBOL: "cobol",
|
|
||||||
COFFEE_SCRIPT: "coffeescript",
|
|
||||||
CONCURNAS: "concurnas",
|
|
||||||
CONTENT_SECURITY_POLICY: "csp",
|
|
||||||
COOKLANG: "cooklang",
|
|
||||||
Coq: "coq",
|
|
||||||
CRYSTAL: "crystal",
|
|
||||||
CSS_EXTRAS: "css-extras",
|
|
||||||
CSV: "csv",
|
|
||||||
CUE: "cue",
|
|
||||||
CYPHER: "cypher",
|
|
||||||
D: "d",
|
|
||||||
DART: "dart",
|
|
||||||
DATA_WEAVE: "dataweave",
|
|
||||||
DAX: "dax",
|
|
||||||
DHALL: "dhall",
|
|
||||||
DIFF: "diff",
|
|
||||||
DJANGO: "django",
|
|
||||||
JINJA2: "jinja2",
|
|
||||||
DNS_ZONE_FILE: "dns-zone-file",
|
|
||||||
DOCKER: "docker",
|
|
||||||
DOT: "dot",
|
|
||||||
EBNF: "ebnf",
|
|
||||||
EDITOR_CONFIG: "editorconfig",
|
|
||||||
EIFFEL: "eiffel",
|
|
||||||
EJS: "ejs",
|
|
||||||
ELIXIR: "elixir",
|
|
||||||
ELM: "elm",
|
|
||||||
EMBEDDED_LUA_TEMPLATING: "etlua",
|
|
||||||
ERB: "erb",
|
|
||||||
ERLANG: "erlang",
|
|
||||||
EXCEL_FORMULA: "excel-formula",
|
|
||||||
FSHARP: "fsharp",
|
|
||||||
FACTOR: "factor",
|
|
||||||
FALSE: "false",
|
|
||||||
FIRESTORE_SECURITY_RULES: "firestore-security-rules",
|
|
||||||
FLOW: "flow",
|
|
||||||
FORTRAN: "fortran",
|
|
||||||
FREEMARKER_TEMPLATE_LANGUAGE: "ftl",
|
|
||||||
GAMEMAKER_LANGUAGE: "gml",
|
|
||||||
GAP: "gap",
|
|
||||||
GCODE: "gcode",
|
|
||||||
GD_SCRIPT: "gdscript",
|
|
||||||
GEDCOM: "gedcom",
|
|
||||||
GETTEXT: "gettext",
|
|
||||||
GHERKIN: "gherkin",
|
|
||||||
GIT: "git",
|
|
||||||
GLSL: "glsl",
|
|
||||||
GN: "gn",
|
|
||||||
GNU_LINKER_SCRIPT: "linker-script",
|
|
||||||
GO: "go",
|
|
||||||
GO_MODULE: "go-module",
|
|
||||||
GRADLE: "gradle",
|
|
||||||
GRAPHQL: "graphql",
|
|
||||||
GROOVY: "groovy",
|
|
||||||
HAML: "haml",
|
|
||||||
HANDLEBARS: "handlebars",
|
|
||||||
HASKELL: "haskell",
|
|
||||||
HAXE: "haxe",
|
|
||||||
HCL: "hcl",
|
|
||||||
HLSL: "hlsl",
|
|
||||||
HOON: "hoon",
|
|
||||||
HTTP: "http",
|
|
||||||
HTTP_PUBLIC_KEY_PINS: "hpkp",
|
|
||||||
HTTP_STRICT_TRANSPORT_SECURITY: "hsts",
|
|
||||||
ICHIGOJAM: "ichigojam",
|
|
||||||
ICON: "icon",
|
|
||||||
ICU_MESSAGE_FORMAT: "icu-message-format",
|
|
||||||
IDRIS: "idris",
|
|
||||||
IGNORE: "ignore",
|
|
||||||
INFORM_7: "inform7",
|
|
||||||
INI: "ini",
|
|
||||||
IO: "io",
|
|
||||||
J: "j",
|
|
||||||
JAVA: "java",
|
|
||||||
JAVADOC: "javadoc",
|
|
||||||
JAVADOC_LIKE: "javadoclike",
|
|
||||||
JAVA_STACK_TRACE: "javastacktrace",
|
|
||||||
JEXL: "jexl",
|
|
||||||
JOLIE: "jolie",
|
|
||||||
JQ: "jq",
|
|
||||||
JSDOC: "jsdoc",
|
|
||||||
JS_EXTRAS: "js-extras",
|
|
||||||
JSON: "json",
|
|
||||||
JSON5: "json5",
|
|
||||||
JSONP: "jsonp",
|
|
||||||
JS_STACK_TRACE: "jsstacktrace",
|
|
||||||
JS_TEMPLATES: "js-templates",
|
|
||||||
JULIA: "julia",
|
|
||||||
KEEPALIVED_CONFIGURE: "keepalived",
|
|
||||||
KEYMAN: "keyman",
|
|
||||||
KOTLIN: "kotlin",
|
|
||||||
KUMIR: "kumir",
|
|
||||||
KUSTO: "kusto",
|
|
||||||
LATEX: "latex",
|
|
||||||
LATTE: "latte",
|
|
||||||
LESS: "less",
|
|
||||||
LILYPOND: "lilypond",
|
|
||||||
LIQUID: "liquid",
|
|
||||||
LISP: "lisp",
|
|
||||||
LIVESCRIPT: "livescript",
|
|
||||||
LLVM: "llvm",
|
|
||||||
LOG: "log",
|
|
||||||
LOLCODE: "lolcode",
|
|
||||||
LUA: "lua",
|
|
||||||
MAGMA: "magma",
|
|
||||||
MAKEFILE: "makefile",
|
|
||||||
MARKDOWN: "markdown",
|
|
||||||
MARKUP_TEMPLATING: "markup-templating",
|
|
||||||
MATA: "mata",
|
|
||||||
MATLAB: "matlab",
|
|
||||||
MAXSCRIPT: "maxscript",
|
|
||||||
MEL: "mel",
|
|
||||||
MERMAID: "mermaid",
|
|
||||||
METAFONT: "metafont",
|
|
||||||
MIZAR: "mizar",
|
|
||||||
MONGODB: "mongodb",
|
|
||||||
MONKEY: "monkey",
|
|
||||||
MOONSCRIPT: "moonscript",
|
|
||||||
N1QL: "n1ql",
|
|
||||||
N4JS: "n4js",
|
|
||||||
NAND_TO_TETRIS_HDL: "nand2tetris",
|
|
||||||
NANINOVEL_SCRIPT: "naniscript",
|
|
||||||
NASM: "nasm",
|
|
||||||
NEON: "neon",
|
|
||||||
NEVOD: "nevod",
|
|
||||||
NGINX: "nginx",
|
|
||||||
NIM: "nim",
|
|
||||||
NIX: "nix",
|
|
||||||
NSIS: "nsis",
|
|
||||||
OBJECTIVE_C: "objectivec",
|
|
||||||
OCAML: "ocaml",
|
|
||||||
ODIN: "odin",
|
|
||||||
OPENCL: "opencl",
|
|
||||||
OPENQASM: "openqasm",
|
|
||||||
OZ: "oz",
|
|
||||||
PARI_GP: "parigp",
|
|
||||||
PARSER: "parser",
|
|
||||||
PASCAL: "pascal",
|
|
||||||
PASCALIGO: "pascaligo",
|
|
||||||
PATROL_SCRIPTING_LANGUAGE: "psl",
|
|
||||||
PC_AXIS: "pcaxis",
|
|
||||||
PEOPLECODE: "peoplecode",
|
|
||||||
PERL: "perl",
|
|
||||||
PHP: "php",
|
|
||||||
PHP_DOC: "phpdoc",
|
|
||||||
PHP_EXTRAS: "php-extras",
|
|
||||||
PLANT_UML: "plant-uml",
|
|
||||||
PL_SQL: "plsql",
|
|
||||||
POWERQUERY: "powerquery",
|
|
||||||
POWERSHELL: "powershell",
|
|
||||||
PROCESSING: "processing",
|
|
||||||
PROLOG: "prolog",
|
|
||||||
PROMQL: "promql",
|
|
||||||
PROPERTIES: "properties",
|
|
||||||
PROTOBUF: "protobuf",
|
|
||||||
PUG: "pug",
|
|
||||||
PUPPET: "puppet",
|
|
||||||
PURE: "pure",
|
|
||||||
PUREBASIC: "purebasic",
|
|
||||||
PURESCRIPT: "purescript",
|
|
||||||
PYTHON: "python",
|
|
||||||
QSHARP: "qsharp",
|
|
||||||
Q: "q",
|
|
||||||
QML: "qml",
|
|
||||||
QORE: "qore",
|
|
||||||
R: "r",
|
|
||||||
RACKET: "racket",
|
|
||||||
RAZOR: "razor",
|
|
||||||
REACT_JSX: "jsx",
|
|
||||||
REACT_TSX: "tsx",
|
|
||||||
REASON: "reason",
|
|
||||||
REGEX: "regex",
|
|
||||||
REGO: "rego",
|
|
||||||
RENPY: "renpy",
|
|
||||||
RESCRIPT: "rescript",
|
|
||||||
REST: "rest",
|
|
||||||
RIP: "rip",
|
|
||||||
ROBOCONF: "roboconf",
|
|
||||||
ROBOT_FRAMEWORK: "robotframework",
|
|
||||||
RUBY: "ruby",
|
|
||||||
RUST: "rust",
|
|
||||||
SAS: "sas",
|
|
||||||
SASS: "sass",
|
|
||||||
SCSS: "scss",
|
|
||||||
SCALA: "scala",
|
|
||||||
SCHEME: "scheme",
|
|
||||||
SHELL_SESSION: "shell-session",
|
|
||||||
SMALI: "smali",
|
|
||||||
SMALLTALK: "smalltalk",
|
|
||||||
SMARTY: "smarty",
|
|
||||||
SML: "sml",
|
|
||||||
SOLIDITY: "solidity",
|
|
||||||
SOLUTION_FILE: "solution-file",
|
|
||||||
SOY: "soy",
|
|
||||||
SPARQL: "sparql",
|
|
||||||
SPLUNK_SPL: "splunk-spl",
|
|
||||||
SQF: "sqf",
|
|
||||||
SQL: "sql",
|
|
||||||
SQUIRREL: "squirrel",
|
|
||||||
STAN: "stan",
|
|
||||||
STATA_ADO: "stata",
|
|
||||||
STRUCTURED_TEXT: "iecst",
|
|
||||||
STYLUS: "stylus",
|
|
||||||
SUPERCOLLIDER: "supercollider",
|
|
||||||
SWIFT: "swift",
|
|
||||||
SYSTEMD: "systemd",
|
|
||||||
T4_TEMPLATING: "t4-templating",
|
|
||||||
T4_CSHARP: "t4-cs",
|
|
||||||
T4_VB: "t4-vb",
|
|
||||||
TAP: "tap",
|
|
||||||
TCL: "tcl",
|
|
||||||
TEMPLATE_TOOLKIT_2: "tt2",
|
|
||||||
TEXTILE: "textile",
|
|
||||||
TOML: "toml",
|
|
||||||
TREMOR: "tremor",
|
|
||||||
TURTLE: "turtle",
|
|
||||||
TWIG: "twig",
|
|
||||||
TYPESCRIPT: "typescript",
|
|
||||||
TYPOSCRIPT: "typoscript",
|
|
||||||
UNREALSCRIPT: "unrealscript",
|
|
||||||
UO_RAZOR_SCRIPT: "uorazor",
|
|
||||||
URI: "uri",
|
|
||||||
V: "v",
|
|
||||||
VALA: "vala",
|
|
||||||
VB_NET: "vbnet",
|
|
||||||
VELOCITY: "velocity",
|
|
||||||
VERILOG: "verilog",
|
|
||||||
VHDL: "vhdl",
|
|
||||||
VIM: "vim",
|
|
||||||
VISUAL_BASIC: "visual-basic",
|
|
||||||
WARP_SCRIPT: "warpscript",
|
|
||||||
WEB_ASSEMBLY: "wasm",
|
|
||||||
WEB_IDL: "web-idl",
|
|
||||||
WGSL: "wgsl",
|
|
||||||
WIKI: "wiki",
|
|
||||||
WOLFRAM: "wolfram",
|
|
||||||
WREN: "wren",
|
|
||||||
XEORA: "xeora",
|
|
||||||
XML_DOC: "xml-doc",
|
|
||||||
XOJO: "xojo",
|
|
||||||
XQUERY: "xquery",
|
|
||||||
YAML: "yaml",
|
|
||||||
YANG: "yang",
|
|
||||||
ZIG: "zig",
|
|
||||||
} as const;
|
|
||||||
export type Language = (typeof Language)[keyof typeof Language];
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
language: string;
|
language: string;
|
||||||
|
class?: string;
|
||||||
|
dark?: boolean;
|
||||||
|
withLineNumbers?: boolean;
|
||||||
} & ComponentProps<"code">;
|
} & ComponentProps<"code">;
|
||||||
|
|
||||||
export const Highlight: ParentComponent<Props> = (_props) => {
|
export const Highlight: ParentComponent<Props> = (_props) => {
|
||||||
@ -326,16 +31,16 @@ export const Highlight: ParentComponent<Props> = (_props) => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const languageClass = createMemo(() => `language-${props.language}`);
|
const languageClass = createMemo(() => `language-${props.language}`);
|
||||||
|
|
||||||
const highlightedCode = createMemo<string | undefined>(() => {
|
const highlightedCode = createMemo<string | undefined>(() => {
|
||||||
const childrenString = props.children?.toString();
|
const childrenString = props.children?.toString();
|
||||||
if (!childrenString) {
|
if (!childrenString) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const grammar = Prismjs.languages[props.language];
|
const grammar = Prismjs.languages[props.language];
|
||||||
if (!grammar) {
|
if (!grammar) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const result = Prismjs.highlight(childrenString, grammar, props.language);
|
const result = Prismjs.highlight(childrenString, grammar, props.language);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -345,15 +50,76 @@ export const Highlight: ParentComponent<Props> = (_props) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCopyToClipboard = () => {
|
||||||
|
if (props.innerHTML) {
|
||||||
|
navigator.clipboard.writeText(props.innerHTML);
|
||||||
|
} else if (props.children) {
|
||||||
|
navigator.clipboard.writeText(props.children.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Copié dans le presse-papier", {
|
||||||
|
duration: 2000,
|
||||||
|
position: "top-right",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre class={clsx("prism-code flex overflow-x-auto pb-6", languageClass())}>
|
<div
|
||||||
|
class={clsx(
|
||||||
|
"group relative flex items-start px-4 py-2 w-full",
|
||||||
|
props.class,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="absolute cursor-pointer top-0 right-2 text-slate-500 bg-slate-200/10 rounded-md hover:bg-linear-to-r hover:from-violet-400/30 hover:via-violet-400 hover:to-violet-400/30 p-px hover:text-violet-300"
|
||||||
|
type="button"
|
||||||
|
onClick={handleCopyToClipboard}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class={clsx(
|
||||||
|
props.dark ? "hover:bg-slate-800" : "hover:bg-white",
|
||||||
|
"p-2 block rounded-md",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span class="sr-only">Copier l'extrait de code</span>
|
||||||
|
<Icon path={clipboard} class="w-5 h-5" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{props.withLineNumbers && props.children?.toString() && (
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="border-r leading-6 border-slate-300/5 pr-4 font-mono text-slate-600 select-none"
|
||||||
|
>
|
||||||
|
<For
|
||||||
|
each={Array.from({
|
||||||
|
length: props.children.toString().split("\n").length,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{(_, index) => (
|
||||||
|
<>
|
||||||
|
{(index() + 1).toString().padStart(2, "0")}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<pre
|
||||||
|
class={clsx(
|
||||||
|
"not-prose w-full prism-code flex overflow-x-auto",
|
||||||
|
languageClass(),
|
||||||
|
)}
|
||||||
|
>
|
||||||
<code
|
<code
|
||||||
class={clsx("px-4", props.class)}
|
class={clsx("px-4", "leading-6")}
|
||||||
innerHTML={highlightedCode()}
|
innerHTML={highlightedCode()}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</code>
|
</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -25,8 +25,6 @@ export function Prose(props: ProseProps) {
|
|||||||
"prose-a:font-semibold",
|
"prose-a:font-semibold",
|
||||||
// link underline
|
// link underline
|
||||||
"prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,var(--color-violet-300))] prose-a:hover:[--tw-prose-underline-size:6px]",
|
"prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,var(--color-violet-300))] prose-a:hover:[--tw-prose-underline-size:6px]",
|
||||||
// pre
|
|
||||||
"prose-pre:rounded-xl prose-pre:bg-slate-900 prose-pre:shadow-lg",
|
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
106
app/components/Snippet.tsx
Normal file
106
app/components/Snippet.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
|
||||||
|
import { For, createSignal } from "solid-js";
|
||||||
|
import { Highlight } from "./Highlight";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
function TrafficLightsIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||||
|
return (
|
||||||
|
<svg aria-hidden="true" viewBox="0 0 42 10" fill="none" {...props}>
|
||||||
|
<circle cx="5" cy="5" r="4.5" class="fill-red-400" />
|
||||||
|
<circle cx="21" cy="5" r="4.5" class="fill-amber-300" />
|
||||||
|
<circle cx="37" cy="5" r="4.5" class="fill-green-500" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnippetTab = {
|
||||||
|
name: string;
|
||||||
|
codeLanguage: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SnippetProps = {
|
||||||
|
children?: JSX.Element;
|
||||||
|
class?: string;
|
||||||
|
snippets: SnippetTab[];
|
||||||
|
dark?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Snippet(props: SnippetProps) {
|
||||||
|
const [selectedTab, setSelectedTab] = createSignal<SnippetTab>(
|
||||||
|
props.snippets[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isActive = (tab: SnippetTab) => selectedTab()?.name === tab.name;
|
||||||
|
const selectTab = (name: string) => {
|
||||||
|
const tab = props.snippets.find((tab) => tab.name === name);
|
||||||
|
if (tab) setSelectedTab(tab);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(
|
||||||
|
"relative rounded-2xl ring-1 ring-white/10 backdrop-blur-sm",
|
||||||
|
props.dark ? "bg-[#0A101F]/80" : "bg-slate-50",
|
||||||
|
props.class,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="absolute -top-px right-11 left-20 h-px bg-linear-to-r from-violet-300/0 via-violet-300/70 to-violet-300/0" />
|
||||||
|
<div class="absolute right-20 -bottom-px left-11 h-px bg-linear-to-r from-purple-400/0 via-purple-400 to-purple-400/0" />
|
||||||
|
<div class="pt-4 pl-4">
|
||||||
|
<TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" />
|
||||||
|
|
||||||
|
<div class="mt-4 flex space-x-2 text-xs">
|
||||||
|
<For each={props.snippets}>
|
||||||
|
{(tab) => (
|
||||||
|
<div
|
||||||
|
class={clsx(
|
||||||
|
"flex h-6 rounded-full",
|
||||||
|
{ "cursor-pointer": tab.codeLanguage && !isActive(tab) },
|
||||||
|
isActive(tab)
|
||||||
|
? clsx(
|
||||||
|
"bg-linear-to-r from-violet-400/30 via-violet-400 to-violet-400/30 p-px font-medium",
|
||||||
|
props.dark ? "text-violet-300" : "text-violet-600",
|
||||||
|
)
|
||||||
|
: props.dark
|
||||||
|
? "text-slate-400"
|
||||||
|
: "text-slate-500",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={clsx(
|
||||||
|
"flex items-center rounded-full px-2.5",
|
||||||
|
isActive(tab) && props.dark
|
||||||
|
? "bg-slate-800"
|
||||||
|
: "bg-violet-100",
|
||||||
|
)}
|
||||||
|
disabled={!tab.codeLanguage}
|
||||||
|
onClick={() => selectTab(tab.name)}
|
||||||
|
>
|
||||||
|
{tab.name}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedTab() && (
|
||||||
|
<div class="mt-6">
|
||||||
|
<Highlight
|
||||||
|
class={clsx(
|
||||||
|
"!pt-0 !px-1 max-h-96 overflow-auto",
|
||||||
|
props.dark && "dark text-white",
|
||||||
|
)}
|
||||||
|
language={selectedTab().codeLanguage}
|
||||||
|
withLineNumbers
|
||||||
|
>
|
||||||
|
{selectedTab().code}
|
||||||
|
</Highlight>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
131
app/components/Tabs.tsx
Normal file
131
app/components/Tabs.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import type { JSX, Accessor, Setter } from "solid-js";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
createSignal,
|
||||||
|
onMount,
|
||||||
|
For,
|
||||||
|
} from "solid-js";
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type TabType = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TabsContextType = {
|
||||||
|
selectedTab: Accessor<string>;
|
||||||
|
setSelectedTab: Setter<string>;
|
||||||
|
tabs: Accessor<TabType[]>;
|
||||||
|
addTab: (tab: TabType) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TabsContext = createContext<TabsContextType>({
|
||||||
|
selectedTab: () => "",
|
||||||
|
setSelectedTab: () => {},
|
||||||
|
tabs: () => [],
|
||||||
|
addTab: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Tabs(props: {
|
||||||
|
defaultSelectedTab?: string;
|
||||||
|
children: JSX.Element;
|
||||||
|
}) {
|
||||||
|
const [selectedTab, setSelectedTab] = createSignal(
|
||||||
|
props.defaultSelectedTab || "",
|
||||||
|
);
|
||||||
|
const [tabs, setTabs] = createSignal<TabType[]>([]);
|
||||||
|
|
||||||
|
const addTab = (tab: TabType) => {
|
||||||
|
console.log("Adding tab", tab);
|
||||||
|
|
||||||
|
setTabs((prevTabs) => {
|
||||||
|
// Append to the end of the array and make sure it's unique
|
||||||
|
if (prevTabs.some((t) => t.value === tab.value)) {
|
||||||
|
return prevTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...prevTabs, tab];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabsContext.Provider
|
||||||
|
value={{
|
||||||
|
selectedTab,
|
||||||
|
setSelectedTab,
|
||||||
|
tabs,
|
||||||
|
addTab,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="max-w-full overflow-x-auto overflow-y-hidden">
|
||||||
|
<ul
|
||||||
|
class="!p-0 w-max flex items-stretch gap-1 !m-0"
|
||||||
|
aria-orientation="horizontal"
|
||||||
|
role="tablist"
|
||||||
|
>
|
||||||
|
<For each={tabs()}>
|
||||||
|
{(tab) => (
|
||||||
|
<li class="overflow-hidden">
|
||||||
|
<TabItem
|
||||||
|
tab={tab}
|
||||||
|
isSelected={selectedTab() === tab.value}
|
||||||
|
select={() => setSelectedTab(tab.value)}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="-mt-1 p-2">{props.children}</div>
|
||||||
|
</div>
|
||||||
|
</TabsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabItem(props: {
|
||||||
|
tab: TabType;
|
||||||
|
isSelected: boolean;
|
||||||
|
select: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={props.isSelected ? "primary" : "secondary"}
|
||||||
|
class={clsx("!rounded-md", props.isSelected && "cursor-default")}
|
||||||
|
onClick={props.select}
|
||||||
|
>
|
||||||
|
{props.tab.label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tabs.Item = (props: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
children: JSX.Element;
|
||||||
|
}) => {
|
||||||
|
const tabsContext = useContext(TabsContext);
|
||||||
|
if (!tabsContext) {
|
||||||
|
throw new Error("Tabs.Item must be used within Tabs");
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
console.log("Mounting tab", props.label);
|
||||||
|
tabsContext.addTab({ label: props.label, value: props.value });
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(
|
||||||
|
"first:!mt-0",
|
||||||
|
"last:!mb-0",
|
||||||
|
tabsContext.selectedTab() !== props.value && "hidden",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,46 +1,80 @@
|
|||||||
pre[class*=language-] {
|
.token.comment {
|
||||||
color: var(--color-slate-50)
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.token.tag,
|
|
||||||
.token.class-name,
|
.dark .token.module,
|
||||||
.token.selector,
|
.dark .token.attr-name,
|
||||||
.token.selector .class,
|
.dark .token.keyword,
|
||||||
.token.selector.class,
|
.dark .token.rule,
|
||||||
|
.dark .token.pseudo-class,
|
||||||
|
.dark .token.important {
|
||||||
|
color: #CB5FDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .token.comment,
|
||||||
|
.dark .token.operator,
|
||||||
|
.dark .token.combinator {
|
||||||
|
color: var(--color-slate-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .token.punctuation,
|
||||||
|
.dark .token.attr-equals {
|
||||||
|
color: var(--color-slate-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .token.attr-value,
|
||||||
|
.dark .token.class,
|
||||||
|
.dark .token.string {
|
||||||
|
color: #89CA6C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .token.property,
|
||||||
|
.dark .token.tag {
|
||||||
|
color: #E3596F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .token.function-variable {
|
||||||
|
color: #61AFEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Light mode */
|
||||||
|
|
||||||
|
.token.attr-value,
|
||||||
|
.token.class,
|
||||||
|
.token.string {
|
||||||
|
color: #50A14F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function-variable,
|
||||||
|
.token.generic-function,
|
||||||
.token.function {
|
.token.function {
|
||||||
color: var(--color-pink-400)
|
color: #4078F2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.token.module,
|
||||||
.token.attr-name,
|
.token.attr-name,
|
||||||
|
.token.keyword,
|
||||||
.token.rule,
|
.token.rule,
|
||||||
.token.pseudo-class,
|
.token.pseudo-class,
|
||||||
.token.important {
|
.token.important {
|
||||||
color: var(--color-slate-300)
|
color: #A626A4;
|
||||||
}
|
}
|
||||||
.token.keyword,
|
|
||||||
.token.module {
|
.token.property,
|
||||||
color: var(--color-pink-400)
|
.token.tag {
|
||||||
|
color: #E55649;
|
||||||
}
|
}
|
||||||
.token.attr-value,
|
|
||||||
.token.class,
|
.token.generic,
|
||||||
.token.string,
|
.token.class-name {
|
||||||
.token.property {
|
color: #C18401;
|
||||||
color: var(--color-sky-300)
|
|
||||||
}
|
|
||||||
.token.punctuation,
|
|
||||||
.token.attr-equals {
|
|
||||||
color: var(--color-slate-500)
|
|
||||||
}
|
|
||||||
.token.unit,
|
|
||||||
.language-css .token.function {
|
|
||||||
color: var(--color-teal-200)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.token.comment,
|
.token.comment,
|
||||||
.token.operator,
|
|
||||||
.token.combinator {
|
.token.combinator {
|
||||||
color: var(--color-slate-400)
|
color: #A0A1A7;
|
||||||
}
|
}
|
||||||
.prism-code {
|
.token.operator,
|
||||||
margin: 0
|
.token.builtin {
|
||||||
}
|
color: #0184BC;
|
||||||
.prism-code + .prism-code {
|
|
||||||
margin-bottom: 1rem
|
|
||||||
}
|
}
|
||||||
@ -1,20 +1,37 @@
|
|||||||
import { Show } from "solid-js";
|
|
||||||
import { usePageContext } from "vike-solid/usePageContext";
|
import { usePageContext } from "vike-solid/usePageContext";
|
||||||
|
import { Link } from "@/components/Link";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { is404 } = usePageContext();
|
const pageContext = usePageContext();
|
||||||
|
|
||||||
|
if (pageContext.is404) {
|
||||||
return (
|
return (
|
||||||
<Show
|
|
||||||
when={is404}
|
|
||||||
fallback={
|
|
||||||
<>
|
<>
|
||||||
<h1>500 Internal Server Error</h1>
|
<div class="max-w-2xl min-w-0 flex-auto px-4 py-16 lg:max-w-none lg:pr-0 lg:pl-8 xl:px-16">
|
||||||
<p>Something went wrong.</p>
|
<div class="flex h-full flex-col items-center justify-center text-center">
|
||||||
|
<p class="font-display text-sm font-medium text-slate-900">404</p>
|
||||||
|
<h1 class="mt-3 font-display text-3xl tracking-tight text-slate-900">
|
||||||
|
Page introuvable
|
||||||
|
</h1>
|
||||||
|
<p class="mt-2 text-sm text-slate-500">
|
||||||
|
Désolé, nous ne pouvons pas trouver la page que vous recherchez.
|
||||||
|
</p>
|
||||||
|
<Link href="/" class="mt-8 text-sm font-medium text-slate-900">
|
||||||
|
Retour à l'accueil
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Erreur {pageContext.abortStatusCode || 500}</h1>
|
||||||
|
<p>
|
||||||
|
{(pageContext.abortReason as string) ??
|
||||||
|
"Une erreur s'est produite lors du chargement de la page."}
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
}
|
|
||||||
>
|
|
||||||
<h1>404 Page Not Found</h1>
|
|
||||||
<p>This page could not be found.</p>
|
|
||||||
</Show>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
35
app/pages/communaute/partages/+Page.mdx
Normal file
35
app/pages/communaute/partages/+Page.mdx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: Partages et réutilisations du contenu de Memento Dev
|
||||||
|
description: "Partagez et réutilisez le contenu de Memento Dev : Exportez, collaborez, intégrez !"
|
||||||
|
tags: []
|
||||||
|
---
|
||||||
|
|
||||||
|
import Callout from "@/components/Callout";
|
||||||
|
|
||||||
|
Tu souhaites partager ou réutiliser le contenu de Memento Dev ?
|
||||||
|
Tout d'abord, permet-moi de te remercier pour ton intérêt ! 🙏
|
||||||
|
|
||||||
|
L'entièreté du contenu de Memento Dev est disponible publiquement et est sous licence **CC BY-NC-SA 4.0**.
|
||||||
|
Cela signifie que tu peux le partager et le réutiliser, tant que tu respectes les conditions de la licence.
|
||||||
|
|
||||||
|
## Conditions de la licence
|
||||||
|
|
||||||
|
La licence **CC BY-NC-SA 4.0** impose les conditions suivantes :
|
||||||
|
|
||||||
|
- **Attribution** : Tu dois donner le crédit approprié, fournir un lien vers la licence et indiquer si des modifications ont été apportées.
|
||||||
|
Tu peux le faire de la manière suivante :
|
||||||
|
- En ajoutant un lien vers la page d'accueil de Memento Dev : [https://memento-dev.fr](https://memento-dev.fr)
|
||||||
|
- En ajoutant un lien vers la licence : [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||||
|
- **Pas d'utilisation commerciale** : Tu ne peux pas utiliser le matériel à des fins commerciales.
|
||||||
|
|
||||||
|
## Écoles, entreprises et utilisateurs réutilisant le contenu de Memento Dev
|
||||||
|
|
||||||
|
Un grand merci à ces entités qui utilisent le contenu de Memento Dev pour leurs formations ou leurs projets ! 🙏
|
||||||
|
|
||||||
|
- [Coda](https://coda.school)
|
||||||
|
- [O'clock](https://oclock.io)
|
||||||
|
- [Wild Code School](https://wildcodeschool.com)
|
||||||
|
|
||||||
|
<Callout type="note" title="Tu utilises mon contenu et tu souhaites apparaître ici ?">
|
||||||
|
Pour figurer sur cette page, tu peux tout simplement m'en faire la demande par [email _(gauthier@gauthierdaniels.fr)_](mailto:gauthier@gauthierdaniels?subject=Demande%20d'ajout%20sur%20la%20page%20partages%20et%20r%C3%A9utilisations%20Memento%20Dev).
|
||||||
|
</Callout>
|
||||||
150
app/pages/docs/react/+Page.mdx
Normal file
150
app/pages/docs/react/+Page.mdx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
title: Introduction à React
|
||||||
|
description: Parlons un peu de React, ce qu'il est, ce qu'il fait et pourquoi il est si populaire.
|
||||||
|
tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)]
|
||||||
|
---
|
||||||
|
|
||||||
|
import Callout from "@/components/Callout";
|
||||||
|
import tabs from "./tabs";
|
||||||
|
|
||||||
|
Parlons peu, parlons bien ! 😄
|
||||||
|
|
||||||
|
React est une **bibliothèque** _(non, pas un **framework** !)_ JavaScript open-source développée par Facebook.
|
||||||
|
Elle est utilisée pour construire des interfaces utilisateur _(UI)_ interactives et dynamiques.
|
||||||
|
|
||||||
|
<Callout type="note" title="Pourquoi React est si populaire ?">
|
||||||
|
- **Facilité d'utilisation** : React est facile à apprendre et à utiliser. Il est basé sur JavaScript, qui est l'un des langages de programmation les plus populaires.
|
||||||
|
- **Réutilisabilité des composants** : React permet de créer des composants réutilisables. Cela signifie que tu peux créer un composant une fois et l'utiliser partout où tu en as besoin.
|
||||||
|
- **Performances** : React utilise un DOM virtuel _(Virtual DOM)_ pour améliorer les performances de l'application.
|
||||||
|
- **Communauté active** : React a une communauté active de développeurs qui contribuent à son développement et partagent des ressources utiles.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
Mais on peut aussi y noter des points faibles bien entendu, car tout n'est pas rose :
|
||||||
|
|
||||||
|
- **Courbe d'apprentissage** : Bien que React soit "facile" à apprendre, les concepts avancés demandent un peu de temps pour être maîtrisés.
|
||||||
|
- **Taille du bundle** : React est relativement lourd en termes de taille de bundle, ce qui peut affecter les performances de l'application en terme de chargement initial.
|
||||||
|
- **GAFAM** : Comme d'autres bibliothèques/frameworks, React n'échappe pas à la critique de la part de certains développeurs qui ne souhaitent pas utiliser des technologies développées par des géants du web.
|
||||||
|
|
||||||
|
## 🤔 Pourquoi une bibliothèque et pas un framework ?
|
||||||
|
|
||||||
|
Très grand débat que voilà ! Vraiment.. il y a des guerres qui se sont déclarées pour moins que ça 😅
|
||||||
|
|
||||||
|
Blague à part, pour pouvoir dire que React n'est pas un framework, il faut comprendre la différence entre les deux :
|
||||||
|
|
||||||
|
- **Framework** : Un framework est un ensemble de bibliothèques et de composants qui sont prédéfinis et structurés pour te permettre de construire une application.
|
||||||
|
En gros, le framework te dit comment faire les choses.
|
||||||
|
- **Bibliothèque** : Une bibliothèque est un ensemble de fonctions et de composants que tu peux utiliser pour construire une application.
|
||||||
|
En gros, c'est toi qui décides comment faire les choses.
|
||||||
|
|
||||||
|
Et si tu connais déjà React, je te vois venir avec tes grands sabots... !
|
||||||
|
|
||||||
|
<Callout type="note" title="React a ses propres règles, on ne peut pas faire n'importe quoi !">
|
||||||
|
C'est vrai ! React a ses propres règles et conventions, mais il te laisse quand même une grande liberté pour organiser ton code comme tu le souhaites.
|
||||||
|
|
||||||
|
Si on se concentre sur la **préoccupation principale** de React, c'est de gérer l'**interface utilisateur** _(UI)_ de ton application.
|
||||||
|
En aucun cas, React _(tel quel et "pour le moment")_ va te dire comment gérer ton état global, comment gérer tes requêtes HTTP, etc.
|
||||||
|
|
||||||
|
Mais tu peux totalement utiliser React **au sein** d'un framework !
|
||||||
|
Tu as notamment des frameworks comme [**Next.js**](https://nextjs.org/) ou [**Gatsby**](https://www.gatsbyjs.com/) qui utilisent React
|
||||||
|
avec des fonctionnalités supplémentaires pour gérer le routage, le rendu côté serveur, etc.
|
||||||
|
|
||||||
|
_(Le meilleur, selon moi, c'est [**Vike**](https://vike.dev/) qui te permet d'utiliser presque n'importe quelle bibliothèque avec une même architecture 😏)_
|
||||||
|
|
||||||
|
Mais concentrons-nous sur React en tant que bibliothèque, et non en tant que framework 😉
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## 📝 JSX
|
||||||
|
|
||||||
|
Ce qui peut être déroutant au premier abord avec React, c'est le **JSX**.
|
||||||
|
On serait tenté de dire que c'est du HTML, mais en fait : **pas du tout** !
|
||||||
|
|
||||||
|
Le JSX est un sucre syntaxique _(syntactic sugar)_ qui permet d'écrire du code JavaScript en se basant sur le système de balisage HTML.
|
||||||
|
|
||||||
|
L'avantage de JSX c'est que le code devient beaucoup plus lisible et plus proche de ce que tu connais déjà avec HTML.
|
||||||
|
Mais il s'agit bien de JavaScript, et non de HTML !
|
||||||
|
|
||||||
|
## 🧩 Composants
|
||||||
|
|
||||||
|
React est basé sur le concept de **composants**. Un composant est une partie réutilisable de l'interface utilisateur _(UI)_ qui peut être affichée à l'écran.
|
||||||
|
|
||||||
|
Dans la majorité des cas, on va chercher à **mutualiser** les composants pour éviter de répéter du code inutilement.
|
||||||
|
L'exemple le plus flagrant sera par exemple tous les boutons de ton application, qui auront probablement tous la même apparence et le même comportement.
|
||||||
|
|
||||||
|
Il est aussi possible de les **imbriquer** les uns dans les autres !
|
||||||
|
En fait, on joue avec des Lego, mais en version code 👷
|
||||||
|
|
||||||
|
Mais si on veut vraiment montrer le potentiel de React, parlons maintenant... 🥁
|
||||||
|
Des **states**, **cycles de vie** et des **props** !
|
||||||
|
|
||||||
|
## 🛠️ States, Cycles de vie et Props
|
||||||
|
|
||||||
|
T'assomer aussi vite avec ces termes qui ne te parlent peut-être pas, c'est pas cool de ma part...
|
||||||
|
Pardon pour les gros mots, je me calme tout de suite ! 🙈
|
||||||
|
|
||||||
|
Si ça te rassure, je vais très rapidement évoquer ce qu'il se cache derrière ces termes barbares,
|
||||||
|
je réserve les détails pour des articles dédiés 😉
|
||||||
|
|
||||||
|
### 🗄️ States
|
||||||
|
|
||||||
|
... ou également appelés **états** en français.
|
||||||
|
|
||||||
|
Le but du state, c'est de stocker des données qui vont être **observées** par React.
|
||||||
|
À chaque fois que le state va être modifié, React va **réagir** et mettre à jour l'interface utilisateur _(UI)_ en conséquence afin d'afficher les nouvelles données.
|
||||||
|
|
||||||
|
### 🔄 Cycles de vie
|
||||||
|
|
||||||
|
Les **cycles de vie** _(lifecycle)_ sont des méthodes qui sont appelées à des moments précis dans le cycle de vie d'un composant React.
|
||||||
|
|
||||||
|
Si tu as lu la section qui parle brièvement des states, tu auras peut-être remarqué cette phrase :
|
||||||
|
|
||||||
|
> À chaque fois que le state va être modifié, React va **réagir** et mettre à jour l'interface utilisateur [...]
|
||||||
|
|
||||||
|
Et bien c'est là que les cycles de vie entrent en jeu !
|
||||||
|
Un composant sur React va avoir un cycle de vie, caractérisé par trois phases :
|
||||||
|
|
||||||
|
1. **Montage du composant** _(Mounting)_ : le composant est créé et inséré dans le DOM.
|
||||||
|
2. **Mise à jour du composant** _(Updating)_ : le composant est mis à jour en fonction des changements de state ou de props.
|
||||||
|
3. **Démontage du composant** _(Unmounting)_ : le composant est retiré du DOM.
|
||||||
|
|
||||||
|
Ces différentes phases vont nous permettre d'interagir avec le composant à des moments précis, et d'effectuer des actions en conséquence.
|
||||||
|
|
||||||
|
### 📦 Props
|
||||||
|
|
||||||
|
Et pour finir, les **props** _(properties ou tout simplement "propriétés" en français)_ !
|
||||||
|
|
||||||
|
Il s'agit ni plus ni moins que des **arguments** que tu vas passer à un composant, comme tu le ferais avec une fonction.
|
||||||
|
|
||||||
|
Cependant il faut noter une chose :
|
||||||
|
|
||||||
|
On transmet les props à un composant précis, qui sera donc un composant **enfant**.
|
||||||
|
Un composant enfant ne pourra pas transmettre des props à un composant parent, c'est unidirectionnel _(mais on verra comment on peut faire autrement 😉)_.
|
||||||
|
|
||||||
|
## 🖥️ Une petite démo ?
|
||||||
|
|
||||||
|
OK, mais vraiment petite !
|
||||||
|
|
||||||
|
Prenons l'exemple d'une application qui servira **uniquement** à afficher une liste de tâches _(une todolist donc !)_.
|
||||||
|
_(Bon... utiliser React uniquement pour ça c'est abusé, mais c'est pour l'exemple 😅)_
|
||||||
|
|
||||||
|
<tabs.reactTodolist />
|
||||||
|
|
||||||
|
On peut très bien imaginer des améliorations à cette application, comme par exemple :
|
||||||
|
|
||||||
|
- Supprimer une tâche
|
||||||
|
- Réinitialiser la liste des tâches
|
||||||
|
- Marquer une tâche comme terminée _(et inversement)_
|
||||||
|
- Ordonner les tâches pour afficher en priorité les tâches non terminées
|
||||||
|
- Enregistrer les tâches dans le navigateur pour les retrouver après un rafraîchissement de la page
|
||||||
|
|
||||||
|
Et si on se gardait ça pour la suite ? 😉
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Tu l'auras compris, React permet de résoudre un certain nombre de problématiques que l'on peut rencontrer lors du développement d'une application web.
|
||||||
|
|
||||||
|
Pas des problématiques majeures, mais ça nous permet tout de même en tant que développeur de gagner du temps et de l'efficacité !
|
||||||
|
|
||||||
|
Dans le cas où le fait que ce soit créé et maintenu par Facebook _(ou GAFAM de manière générale)_ est contre tes valeurs,
|
||||||
|
tu as des solutions très semblables qui existent, comme [**SolidJS**](https://www.solidjs.com/) par exemple.
|
||||||
|
|
||||||
|
Et si tu veux en savoir plus, je t'invite à lire les articles suivants qui vont te permettre de rentrer un peu plus dans le détail de React ! 🚀
|
||||||
86
app/pages/docs/react/tabs.tsx
Normal file
86
app/pages/docs/react/tabs.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { Snippet } from "@/components/Snippet";
|
||||||
|
|
||||||
|
const reactTodoListSnippets = [
|
||||||
|
{
|
||||||
|
name: "App.tsx",
|
||||||
|
codeLanguage: "tsx",
|
||||||
|
code: `import TodoList from "./TodoList";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>TodoList</h1>
|
||||||
|
<TodoList />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TodoList.tsx",
|
||||||
|
codeLanguage: "tsx",
|
||||||
|
code: `import TodoListItem from "./TodoListItem";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const TodoList = () => {
|
||||||
|
const [items, setItems] = React.useState<string[]>([]);
|
||||||
|
const [inputValue, setInputValue] = React.useState<string>("");
|
||||||
|
|
||||||
|
const handleInputValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInputValue(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
// On empêche le comportement par défaut du formulaire
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// On ajoute un nouvel élément à la liste des tâches
|
||||||
|
setItems([...items, inputValue]);
|
||||||
|
|
||||||
|
// On réinitialise la valeur de l'input
|
||||||
|
setInputValue("");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<label htmlFor="todolist-input"></label>
|
||||||
|
|
||||||
|
<input id="todolist-input" type="text" value={inputValue} onChange={handleInputValueChange} />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
<TodoListItem item={item} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoList;`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TodoListItem.tsx",
|
||||||
|
codeLanguage: "tsx",
|
||||||
|
code: `import React from "react";
|
||||||
|
|
||||||
|
interface TodoListItemProps {
|
||||||
|
item: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TodoListItem = (props: TodoListItemProps) => {
|
||||||
|
return <span>{props.item}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoListItem;`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
reactTodolist: () => {
|
||||||
|
return <Snippet snippets={reactTodoListSnippets} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -2,26 +2,35 @@ import type { JSX } from "solid-js";
|
|||||||
|
|
||||||
import blurIndigoImage from "@/images/blur-indigo.webp";
|
import blurIndigoImage from "@/images/blur-indigo.webp";
|
||||||
import blurCyanImage from "@/images/blur-cyan.webp";
|
import blurCyanImage from "@/images/blur-cyan.webp";
|
||||||
import { Highlight } from "@/components/Highlight";
|
|
||||||
import { HeroBackground } from "./HeroBackground";
|
import { HeroBackground } from "./HeroBackground";
|
||||||
|
import { Snippet } from "@/components/Snippet";
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { Image } from "@/components/Image";
|
import { Image } from "@/components/Image";
|
||||||
import { For } from "solid-js";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
const codeLanguage = "javascript";
|
const snippets = [
|
||||||
const code = `export default {
|
{
|
||||||
role: 'developer',
|
name: "memento-dev.config.js",
|
||||||
|
codeLanguage: "javascript",
|
||||||
|
code: `export default {
|
||||||
|
role: "developer",
|
||||||
qualifications: [
|
qualifications: [
|
||||||
'DWWM',
|
"DWWM",
|
||||||
'CDA',
|
"CDA",
|
||||||
'CDUI',
|
"CDUI",
|
||||||
]
|
]
|
||||||
}`;
|
}`,
|
||||||
|
},
|
||||||
const tabs = [
|
{
|
||||||
{ name: "memento-dev.config.js", isActive: true },
|
name: "package.json",
|
||||||
{ name: "package.json", isActive: false },
|
codeLanguage: "json",
|
||||||
|
code: `{
|
||||||
|
"name": "memento-dev",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "Memento Dev est une plateforme open-source, soutenue et maintenue par une communauté de contributeurs passionnés.",
|
||||||
|
"main": "index.ts",
|
||||||
|
"license": "MIT"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function TrafficLightsIcon(props: JSX.IntrinsicElements["svg"]) {
|
function TrafficLightsIcon(props: JSX.IntrinsicElements["svg"]) {
|
||||||
@ -87,55 +96,8 @@ export function HeroSection() {
|
|||||||
/>
|
/>
|
||||||
<div class="absolute inset-0 rounded-2xl bg-linear-to-tr from-violet-300 via-violet-300/70 to-purple-300 opacity-10 blur-lg" />
|
<div class="absolute inset-0 rounded-2xl bg-linear-to-tr from-violet-300 via-violet-300/70 to-purple-300 opacity-10 blur-lg" />
|
||||||
<div class="absolute inset-0 rounded-2xl bg-linear-to-tr from-violet-300 via-violet-300/70 to-purple-300 opacity-10" />
|
<div class="absolute inset-0 rounded-2xl bg-linear-to-tr from-violet-300 via-violet-300/70 to-purple-300 opacity-10" />
|
||||||
<div class="relative rounded-2xl bg-[#0A101F]/80 ring-1 ring-white/10 backdrop-blur-sm">
|
|
||||||
<div class="absolute -top-px right-11 left-20 h-px bg-linear-to-r from-violet-300/0 via-violet-300/70 to-violet-300/0" />
|
|
||||||
<div class="absolute right-20 -bottom-px left-11 h-px bg-linear-to-r from-purple-400/0 via-purple-400 to-purple-400/0" />
|
|
||||||
<div class="pt-4 pl-4">
|
|
||||||
<TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" />
|
|
||||||
<div class="mt-4 flex space-x-2 text-xs">
|
|
||||||
<For each={tabs}>
|
|
||||||
{(tab) => (
|
|
||||||
<div
|
|
||||||
class={clsx(
|
|
||||||
"flex h-6 rounded-full",
|
|
||||||
tab.isActive
|
|
||||||
? "bg-linear-to-r from-violet-400/30 via-violet-400 to-violet-400/30 p-px font-medium text-violet-300"
|
|
||||||
: "text-slate-500",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={clsx(
|
|
||||||
"flex items-center rounded-full px-2.5",
|
|
||||||
tab.isActive && "bg-slate-800",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{tab.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6 flex items-start px-1 text-sm">
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="border-r border-slate-300/5 pr-4 font-mono text-slate-600 select-none"
|
|
||||||
>
|
|
||||||
<For
|
|
||||||
each={Array.from({ length: code.split("\n").length })}
|
|
||||||
>
|
|
||||||
{(_, index) => (
|
|
||||||
<>
|
|
||||||
{(index() + 1).toString().padStart(2, "0")}
|
|
||||||
<br />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Highlight language={codeLanguage}>{code}</Highlight>
|
<Snippet class="min-h-64" dark snippets={snippets} />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -58,7 +58,7 @@ function NavigationItem(props: NavigationItemProps) {
|
|||||||
</button>
|
</button>
|
||||||
</h3>
|
</h3>
|
||||||
{isOpened() && (
|
{isOpened() && (
|
||||||
<ul class="!mt-0 ml-2 space-y-1 border-l-2 border-slate-100 lg:mt-4 lg:space-y-2 lg:border-slate-200 mb-2">
|
<ul class="!mt-0 ml-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-1 lg:border-slate-200 mb-2">
|
||||||
<For each={props.section.links}>
|
<For each={props.section.links}>
|
||||||
{(link) => (
|
{(link) => (
|
||||||
<li class="relative">
|
<li class="relative">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user