Compare commits
3 Commits
08c5ecfb6a
...
602669a9c0
| Author | SHA1 | Date | |
|---|---|---|---|
| 602669a9c0 | |||
| 8418ca4a74 | |||
| 6a5ade483b |
@ -42,6 +42,6 @@ export function Button(props: ButtonProps) {
|
||||
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 { 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 toast from "solid-toast";
|
||||
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 = {
|
||||
language: string;
|
||||
class?: string;
|
||||
dark?: boolean;
|
||||
withLineNumbers?: boolean;
|
||||
} & ComponentProps<"code">;
|
||||
|
||||
export const Highlight: ParentComponent<Props> = (_props) => {
|
||||
@ -326,16 +31,16 @@ export const Highlight: ParentComponent<Props> = (_props) => {
|
||||
]);
|
||||
|
||||
const languageClass = createMemo(() => `language-${props.language}`);
|
||||
|
||||
const highlightedCode = createMemo<string | undefined>(() => {
|
||||
const childrenString = props.children?.toString();
|
||||
if (!childrenString) {
|
||||
return;
|
||||
}
|
||||
if (!childrenString) return;
|
||||
|
||||
const grammar = Prismjs.languages[props.language];
|
||||
if (!grammar) {
|
||||
return;
|
||||
}
|
||||
if (!grammar) return;
|
||||
|
||||
const result = Prismjs.highlight(childrenString, grammar, props.language);
|
||||
|
||||
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 (
|
||||
<pre class={clsx("prism-code flex overflow-x-auto pb-6", languageClass())}>
|
||||
<code
|
||||
class={clsx("px-4", props.class)}
|
||||
innerHTML={highlightedCode()}
|
||||
{...rest}
|
||||
<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}
|
||||
>
|
||||
{props.children}
|
||||
</code>
|
||||
</pre>
|
||||
<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
|
||||
class={clsx("px-4", "leading-6")}
|
||||
innerHTML={highlightedCode()}
|
||||
{...rest}
|
||||
>
|
||||
{props.children}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -25,8 +25,6 @@ export function Prose(props: ProseProps) {
|
||||
"prose-a:font-semibold",
|
||||
// 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]",
|
||||
// pre
|
||||
"prose-pre:rounded-xl prose-pre:bg-slate-900 prose-pre:shadow-lg",
|
||||
)}
|
||||
{...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-] {
|
||||
color: var(--color-slate-50)
|
||||
.token.comment {
|
||||
font-style: italic;
|
||||
}
|
||||
.token.tag,
|
||||
.token.class-name,
|
||||
.token.selector,
|
||||
.token.selector .class,
|
||||
.token.selector.class,
|
||||
|
||||
.dark .token.module,
|
||||
.dark .token.attr-name,
|
||||
.dark .token.keyword,
|
||||
.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 {
|
||||
color: var(--color-pink-400)
|
||||
color: #4078F2;
|
||||
}
|
||||
|
||||
.token.module,
|
||||
.token.attr-name,
|
||||
.token.keyword,
|
||||
.token.rule,
|
||||
.token.pseudo-class,
|
||||
.token.important {
|
||||
color: var(--color-slate-300)
|
||||
color: #A626A4;
|
||||
}
|
||||
.token.keyword,
|
||||
.token.module {
|
||||
color: var(--color-pink-400)
|
||||
|
||||
.token.property,
|
||||
.token.tag {
|
||||
color: #E55649;
|
||||
}
|
||||
.token.attr-value,
|
||||
.token.class,
|
||||
.token.string,
|
||||
.token.property {
|
||||
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.generic,
|
||||
.token.class-name {
|
||||
color: #C18401;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.operator,
|
||||
.token.combinator {
|
||||
color: var(--color-slate-400)
|
||||
}
|
||||
.prism-code {
|
||||
margin: 0
|
||||
}
|
||||
.prism-code + .prism-code {
|
||||
margin-bottom: 1rem
|
||||
color: #A0A1A7;
|
||||
}
|
||||
.token.operator,
|
||||
.token.builtin {
|
||||
color: #0184BC;
|
||||
}
|
||||
@ -1,20 +1,37 @@
|
||||
import { Show } from "solid-js";
|
||||
import { usePageContext } from "vike-solid/usePageContext";
|
||||
import { Link } from "@/components/Link";
|
||||
|
||||
export default function Page() {
|
||||
const { is404 } = usePageContext();
|
||||
return (
|
||||
<Show
|
||||
when={is404}
|
||||
fallback={
|
||||
<>
|
||||
<h1>500 Internal Server Error</h1>
|
||||
<p>Something went wrong.</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<h1>404 Page Not Found</h1>
|
||||
<p>This page could not be found.</p>
|
||||
</Show>
|
||||
);
|
||||
const pageContext = usePageContext();
|
||||
|
||||
if (pageContext.is404) {
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 blurCyanImage from "@/images/blur-cyan.webp";
|
||||
import { Highlight } from "@/components/Highlight";
|
||||
import { HeroBackground } from "./HeroBackground";
|
||||
import { Snippet } from "@/components/Snippet";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Image } from "@/components/Image";
|
||||
import { For } from "solid-js";
|
||||
import clsx from "clsx";
|
||||
|
||||
const codeLanguage = "javascript";
|
||||
const code = `export default {
|
||||
role: 'developer',
|
||||
qualifications: [
|
||||
'DWWM',
|
||||
'CDA',
|
||||
'CDUI',
|
||||
]
|
||||
}`;
|
||||
|
||||
const tabs = [
|
||||
{ name: "memento-dev.config.js", isActive: true },
|
||||
{ name: "package.json", isActive: false },
|
||||
const snippets = [
|
||||
{
|
||||
name: "memento-dev.config.js",
|
||||
codeLanguage: "javascript",
|
||||
code: `export default {
|
||||
role: "developer",
|
||||
qualifications: [
|
||||
"DWWM",
|
||||
"CDA",
|
||||
"CDUI",
|
||||
]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "package.json",
|
||||
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"]) {
|
||||
@ -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" />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Snippet class="min-h-64" dark snippets={snippets} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -58,7 +58,7 @@ function NavigationItem(props: NavigationItemProps) {
|
||||
</button>
|
||||
</h3>
|
||||
{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}>
|
||||
{(link) => (
|
||||
<li class="relative">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user