Refactor tool catalog into documentation layout
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
This commit is contained in:
parent
d53d4160af
commit
a9b5ca423a
12 changed files with 1925 additions and 114 deletions
|
|
@ -267,7 +267,26 @@ Style courant :
|
||||||
- pas de hover sur les panneaux de lecture ;
|
- pas de hover sur les panneaux de lecture ;
|
||||||
- implementation via le mixin `gold-panel-frame` dans `website/src/styles/_mixins.scss`.
|
- implementation via le mixin `gold-panel-frame` dans `website/src/styles/_mixins.scss`.
|
||||||
|
|
||||||
## 10. Boutons
|
## 10. Catalogue Documentation
|
||||||
|
|
||||||
|
La page catalogue des outils utilise un layout de documentation.
|
||||||
|
|
||||||
|
Regles :
|
||||||
|
|
||||||
|
- hero court, puis contenu principal en deux colonnes sur desktop ;
|
||||||
|
- sommaire a gauche en panneau sticky, avec categories puis liens d'outils de la categorie active ;
|
||||||
|
- une seule categorie d'usage est affichee a droite a la fois ;
|
||||||
|
- la categorie active commence par un mini-hero descriptif, sans compteur decoratif ;
|
||||||
|
- chaque fiche commence par le titre, l'icone, le badge de categorie et une description directe de l'outil ;
|
||||||
|
- l'exemple interactif reste visible en permanence et centre horizontalement ;
|
||||||
|
- les reperes de boutons propres a l'outil sont places a gauche de l'exemple, puis empiles au-dessus sur mobile ;
|
||||||
|
- les reperes sont groupes par categorie de controles de l'outil, avec apercu visuel du bouton et label court ;
|
||||||
|
- les descriptions de reperes sont reservees aux comportements non evidents, par exemple un bouton qui agit sur toute la ligne ou une action de drag & drop ;
|
||||||
|
- les fonctionnalites detaillees et formats d'import texte sont presentes sous l'exemple dans un panneau repliable pour ne pas alourdir la lecture avant manipulation ;
|
||||||
|
- les controles propres au catalogue, comme la reinitialisation d'une demo, ne sont pas documentes comme fonctionnalites de l'outil ;
|
||||||
|
- sur mobile, le sommaire devient un bloc normal place avant les fiches, sans position sticky.
|
||||||
|
|
||||||
|
## 11. Boutons
|
||||||
|
|
||||||
### Primary
|
### Primary
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,22 @@ export function validateSiteContent(site) {
|
||||||
"library.summaryLabel",
|
"library.summaryLabel",
|
||||||
"library.tocLabel",
|
"library.tocLabel",
|
||||||
"library.tocTitle",
|
"library.tocTitle",
|
||||||
|
"library.docTocTitle",
|
||||||
"library.tocDescription",
|
"library.tocDescription",
|
||||||
"library.toolsLabel",
|
"library.toolsLabel",
|
||||||
|
"library.categoryLabel",
|
||||||
|
"library.featureHeading",
|
||||||
|
"library.controlLegendHeading",
|
||||||
|
"library.categories.notesTracking",
|
||||||
|
"library.categories.references",
|
||||||
|
"library.categories.calculationData",
|
||||||
|
"library.categories.timeRoutines",
|
||||||
|
"library.categories.buildsCommands",
|
||||||
|
"library.categoryDescriptions.notesTracking",
|
||||||
|
"library.categoryDescriptions.references",
|
||||||
|
"library.categoryDescriptions.calculationData",
|
||||||
|
"library.categoryDescriptions.timeRoutines",
|
||||||
|
"library.categoryDescriptions.buildsCommands",
|
||||||
"home.hero.eyebrow",
|
"home.hero.eyebrow",
|
||||||
"home.hero.title",
|
"home.hero.title",
|
||||||
"home.hero.description",
|
"home.hero.description",
|
||||||
|
|
@ -442,6 +456,45 @@ export function validateSiteContent(site) {
|
||||||
assert.equal(typeof item, "object", `library.summaryItems[${index}] must be an object`);
|
assert.equal(typeof item, "object", `library.summaryItems[${index}] must be an object`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const libraryToolDocs = valueAt(site, "library.toolDocs");
|
||||||
|
["notepad", "checklist", "counters", "images", "imageAnnotation", "links", "calculator", "table", "timer", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => {
|
||||||
|
const docs = libraryToolDocs?.[toolType];
|
||||||
|
assert.equal(typeof docs, "object", `library.toolDocs.${toolType} must be an object`);
|
||||||
|
assert.ok(Array.isArray(docs.controls), `library.toolDocs.${toolType}.controls must be an array`);
|
||||||
|
assert.ok(docs.controls.length >= 1, `library.toolDocs.${toolType}.controls must not be empty`);
|
||||||
|
docs.controls.forEach((control, index) => {
|
||||||
|
assert.equal(typeof control, "object", `library.toolDocs.${toolType}.controls[${index}] must be an object`);
|
||||||
|
assertNonEmptyString({ control }, "control.group");
|
||||||
|
assertNonEmptyString({ control }, "control.icon");
|
||||||
|
assertNonEmptyString({ control }, "control.title");
|
||||||
|
if (control.text) assertNonEmptyString({ control }, "control.text");
|
||||||
|
if (control.preview) {
|
||||||
|
assert.equal(typeof control.preview, "object", `library.toolDocs.${toolType}.controls[${index}].preview must be an object`);
|
||||||
|
assertNonEmptyString({ preview: control.preview }, "preview.kind");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert.ok(Array.isArray(docs.features), `library.toolDocs.${toolType}.features must be an array`);
|
||||||
|
assert.ok(docs.features.length >= 1, `library.toolDocs.${toolType}.features must not be empty`);
|
||||||
|
docs.features.forEach((feature, index) => {
|
||||||
|
assertNonEmptyString({ feature }, "feature.title");
|
||||||
|
assertNonEmptyString({ feature }, "feature.text");
|
||||||
|
assert.equal(typeof feature, "object", `library.toolDocs.${toolType}.features[${index}] must be an object`);
|
||||||
|
if (feature.details) {
|
||||||
|
assert.ok(Array.isArray(feature.details), `library.toolDocs.${toolType}.features[${index}].details must be an array`);
|
||||||
|
assert.ok(feature.details.length >= 1, `library.toolDocs.${toolType}.features[${index}].details must not be empty`);
|
||||||
|
feature.details.forEach((detail, detailIndex) => {
|
||||||
|
assert.equal(typeof detail, "string", `library.toolDocs.${toolType}.features[${index}].details[${detailIndex}] must be a string`);
|
||||||
|
assert.ok(detail.trim(), `library.toolDocs.${toolType}.features[${index}].details[${detailIndex}] must not be empty`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (docs.importFormat) {
|
||||||
|
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.title");
|
||||||
|
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.text");
|
||||||
|
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.example");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const aboutLimits = valueAt(site, "about.limits");
|
const aboutLimits = valueAt(site, "about.limits");
|
||||||
assert.ok(Array.isArray(aboutLimits), "about.limits must be an array");
|
assert.ok(Array.isArray(aboutLimits), "about.limits must be an array");
|
||||||
assert.ok(aboutLimits.length >= 3, "about.limits must contain at least 3 items");
|
assert.ok(aboutLimits.length >= 3, "about.limits must contain at least 3 items");
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
2
website/public/static/icons/book.svg
Normal file
2
website/public/static/icons/book.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#020202;stroke-miterlimit:10;stroke-width:1.91px;}</style></defs><rect class="cls-1" x="7.23" y="5.32" width="9.55" height="3.82"/><path class="cls-1" d="M20.59,22.5H5.32A1.91,1.91,0,0,1,4,19.24a1.89,1.89,0,0,1,1.35-.56H20.59"/><line class="cls-1" x1="19.64" y1="18.68" x2="19.64" y2="22.5"/><path class="cls-1" d="M20.59,1.5V18.68H5.32a1.91,1.91,0,0,0-1.91,1.91V3.41A1.92,1.92,0,0,1,5.32,1.5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 680 B |
|
|
@ -22,7 +22,10 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
|
||||||
<a className={`nav-item ${route.startsWith("/games") ? "active" : ""}`} href="#/games"><span className="nav-icon nav-icon-controller" aria-hidden="true" /><strong>{content.navigation.games}</strong></a>
|
<a className={`nav-item ${route.startsWith("/games") ? "active" : ""}`} href="#/games"><span className="nav-icon nav-icon-controller" aria-hidden="true" /><strong>{content.navigation.games}</strong></a>
|
||||||
<a className={`nav-item ${route.startsWith("/toolboxes") || route.startsWith("/toolbox") ? "active" : ""}`} href="#/toolboxes"><span className="nav-icon nav-icon-toolbox" aria-hidden="true" /><strong>{content.navigation.toolboxes}</strong></a>
|
<a className={`nav-item ${route.startsWith("/toolboxes") || route.startsWith("/toolbox") ? "active" : ""}`} href="#/toolboxes"><span className="nav-icon nav-icon-toolbox" aria-hidden="true" /><strong>{content.navigation.toolboxes}</strong></a>
|
||||||
</nav>
|
</nav>
|
||||||
<a className={`sidebar-about-link ${route === "/about" ? "active" : ""}`} href="#/about"><span className="sidebar-about-icon" aria-hidden="true">?</span><strong>{content.navigation.about}</strong></a>
|
<div className="sidebar-secondary-links">
|
||||||
|
<a className={`sidebar-about-link ${route === "/about" ? "active" : ""}`} href="#/about"><span className="sidebar-about-icon" aria-hidden="true">?</span><strong>{content.navigation.about}</strong></a>
|
||||||
|
<a className={`sidebar-about-link ${route === "/library" ? "active" : ""}`} href="#/library"><span className="sidebar-about-icon" aria-hidden="true"><Icon name="book" /></span><strong>{content.library.eyebrow}</strong></a>
|
||||||
|
</div>
|
||||||
<div className="sidebar-note">
|
<div className="sidebar-note">
|
||||||
<span className="badge">{content.sidebar.badge}</span>
|
<span className="badge">{content.sidebar.badge}</span>
|
||||||
<p>{content.sidebar.note}</p>
|
<p>{content.sidebar.note}</p>
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions,
|
||||||
<p className="eyebrow">{content.eyebrow}</p>
|
<p className="eyebrow">{content.eyebrow}</p>
|
||||||
<h1>{content.title}</h1>
|
<h1>{content.title}</h1>
|
||||||
<p>{content.summary}</p>
|
<p>{content.summary}</p>
|
||||||
<a className="button hero-library-button" href="#/library">{content.libraryLink}</a>
|
<a className="button hero-library-button library-link-button" href="#/library"><Icon name="book" />{content.libraryLink}</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="toolbar toolbox-actions-row">
|
<section className="toolbar toolbox-actions-row">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// Rôle : présente le projet, son fonctionnement local et les repères de contribution.
|
// Rôle : présente le projet, son fonctionnement local et les repères de contribution.
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { Icon } from "../components/Icon.jsx";
|
||||||
import { RichText } from "../components/RichText.jsx";
|
import { RichText } from "../components/RichText.jsx";
|
||||||
|
|
||||||
export function AboutPage({ siteContent }) {
|
export function AboutPage({ siteContent }) {
|
||||||
|
|
@ -65,7 +66,7 @@ export function AboutPage({ siteContent }) {
|
||||||
<p className="eyebrow">Toolbox</p>
|
<p className="eyebrow">Toolbox</p>
|
||||||
<h2>{content.toolsTitle}</h2>
|
<h2>{content.toolsTitle}</h2>
|
||||||
</div>
|
</div>
|
||||||
<a className="button" href="#/library">{content.toolsLibraryLink}</a>
|
<a className="button library-link-button" href="#/library"><Icon name="book" />{content.toolsLibraryLink}</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="about-tools-list">
|
<div className="about-tools-list">
|
||||||
{content.tools.map((tool) => (
|
{content.tools.map((tool) => (
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,14 @@ const MODULE_COMPONENTS = {
|
||||||
imageAnnotation: { label: "Annotation d'images", mode: "Image + marqueurs", icon: "map", Component: ImageAnnotationModule, editable: true }
|
imageAnnotation: { label: "Annotation d'images", mode: "Image + marqueurs", icon: "map", Component: ImageAnnotationModule, editable: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LIBRARY_CATEGORY_DEFINITIONS = [
|
||||||
|
{ key: "notesTracking", types: ["notepad", "checklist", "counters"] },
|
||||||
|
{ key: "references", types: ["images", "imageAnnotation", "links"] },
|
||||||
|
{ key: "calculationData", types: ["calculator", "table"] },
|
||||||
|
{ key: "timeRoutines", types: ["timer", "taskPlanner"] },
|
||||||
|
{ key: "buildsCommands", types: ["combos", "equipmentPlanner"] }
|
||||||
|
];
|
||||||
|
|
||||||
function cloneData(value) {
|
function cloneData(value) {
|
||||||
if (value === undefined) return {};
|
if (value === undefined) return {};
|
||||||
if (typeof structuredClone === "function") return structuredClone(value);
|
if (typeof structuredClone === "function") return structuredClone(value);
|
||||||
|
|
@ -75,6 +83,10 @@ function getLibraryAnchorId(module) {
|
||||||
return `library-tool-${String(module?.id || module?.type || "outil").replace(/[^a-z0-9_-]+/gi, "-").toLowerCase()}`;
|
return `library-tool-${String(module?.id || module?.type || "outil").replace(/[^a-z0-9_-]+/gi, "-").toLowerCase()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLibraryCategoryAnchorId(category) {
|
||||||
|
return `library-category-${category.key}`;
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentLibraryAnchor() {
|
function getCurrentLibraryAnchor() {
|
||||||
const match = location.hash.match(/^#\/library#(.+)$/);
|
const match = location.hash.match(/^#\/library#(.+)$/);
|
||||||
return match ? decodeURIComponent(match[1]) : "";
|
return match ? decodeURIComponent(match[1]) : "";
|
||||||
|
|
@ -88,16 +100,36 @@ function scrollToLibraryTool(anchorId, replace = false) {
|
||||||
window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
|
window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findCategoryForAnchor(categories, anchorId) {
|
||||||
|
return categories.find((category) => (
|
||||||
|
getLibraryCategoryAnchorId(category) === anchorId ||
|
||||||
|
category.modules.some((module) => getLibraryAnchorId(module) === anchorId)
|
||||||
|
)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
function getToolDescription(siteContent, definition) {
|
function getToolDescription(siteContent, definition) {
|
||||||
const tools = siteContent.about?.tools || [];
|
const tools = siteContent.about?.tools || [];
|
||||||
return tools.find((tool) => tool.name === definition.label)?.description || "";
|
return tools.find((tool) => tool.name === definition.label)?.description || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLibraryCategories(modules, content) {
|
||||||
|
const modulesByType = new Map((modules || []).map((module) => [module.type, module]));
|
||||||
|
return LIBRARY_CATEGORY_DEFINITIONS.map((category) => ({
|
||||||
|
...category,
|
||||||
|
title: content.categories?.[category.key] || category.key,
|
||||||
|
description: content.categoryDescriptions?.[category.key] || "",
|
||||||
|
modules: category.types.map((type) => modulesByType.get(type)).filter(Boolean)
|
||||||
|
})).filter((category) => category.modules.length);
|
||||||
|
}
|
||||||
|
|
||||||
export function LibraryPage({ siteContent, actions }) {
|
export function LibraryPage({ siteContent, actions }) {
|
||||||
const content = siteContent.library;
|
const content = siteContent.library;
|
||||||
const [libraryPayload, setLibraryPayload] = useState(null);
|
const [libraryPayload, setLibraryPayload] = useState(null);
|
||||||
const [libraryError, setLibraryError] = useState("");
|
const [libraryError, setLibraryError] = useState("");
|
||||||
const [moduleData, setModuleData] = useState({});
|
const [moduleData, setModuleData] = useState({});
|
||||||
|
const [activeCategoryKey, setActiveCategoryKey] = useState("");
|
||||||
|
const libraryCategories = useMemo(() => getLibraryCategories(libraryPayload?.toolbox?.modules || [], content), [content, libraryPayload]);
|
||||||
|
const activeCategory = libraryCategories.find((category) => category.key === activeCategoryKey) || libraryCategories[0] || null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
@ -118,11 +150,13 @@ export function LibraryPage({ siteContent, actions }) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!libraryPayload) return;
|
if (!libraryPayload || !libraryCategories.length) return;
|
||||||
const anchorId = getCurrentLibraryAnchor();
|
const anchorId = getCurrentLibraryAnchor();
|
||||||
|
const category = findCategoryForAnchor(libraryCategories, anchorId) || libraryCategories[0];
|
||||||
|
setActiveCategoryKey(category.key);
|
||||||
if (!anchorId) return;
|
if (!anchorId) return;
|
||||||
requestAnimationFrame(() => scrollToLibraryTool(anchorId));
|
requestAnimationFrame(() => scrollToLibraryTool(anchorId));
|
||||||
}, [libraryPayload]);
|
}, [libraryPayload, libraryCategories]);
|
||||||
|
|
||||||
const moduleContext = useMemo(() => ({
|
const moduleContext = useMemo(() => ({
|
||||||
getModuleData: (toolboxId, moduleId, fallback) => moduleData[`${toolboxId}:${moduleId}`] || fallback,
|
getModuleData: (toolboxId, moduleId, fallback) => moduleData[`${toolboxId}:${moduleId}`] || fallback,
|
||||||
|
|
@ -203,52 +237,94 @@ export function LibraryPage({ siteContent, actions }) {
|
||||||
{!libraryError && !libraryPayload && <section className="empty"><p>Chargement des exemples...</p></section>}
|
{!libraryError && !libraryPayload && <section className="empty"><p>Chargement des exemples...</p></section>}
|
||||||
{libraryPayload && (
|
{libraryPayload && (
|
||||||
<>
|
<>
|
||||||
<nav className="library-toc nebula-panel" aria-label={content.tocLabel || "Sommaire des outils"}>
|
<div className="library-doc-layout">
|
||||||
<div className="library-toc-heading">
|
<LibraryDocNav
|
||||||
<h2>{content.tocTitle || "Sommaire"}</h2>
|
content={content}
|
||||||
<p>{content.tocDescription || "Accédez directement à un exemple d’outil dans la page."}</p>
|
categories={libraryCategories}
|
||||||
</div>
|
activeCategoryKey={activeCategory?.key || ""}
|
||||||
<div className="library-toc-links">
|
onSelectCategory={(category, anchorId = getLibraryCategoryAnchorId(category)) => {
|
||||||
{libraryPayload.toolbox.modules.map((module) => {
|
setActiveCategoryKey(category.key);
|
||||||
const definition = MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad;
|
requestAnimationFrame(() => scrollToLibraryTool(anchorId, true));
|
||||||
const title = module.title || definition.label || "Outil";
|
}}
|
||||||
const anchorId = getLibraryAnchorId(module);
|
/>
|
||||||
return (
|
<section className="library-tools" aria-label={content.toolsLabel}>
|
||||||
<a
|
{activeCategory && (
|
||||||
key={module.id}
|
<section className="library-tool-category" key={activeCategory.key} aria-labelledby={getLibraryCategoryAnchorId(activeCategory)}>
|
||||||
href={`#/library#${anchorId}`}
|
<div className="library-category-hero">
|
||||||
onClick={(event) => {
|
<div>
|
||||||
event.preventDefault();
|
<p className="eyebrow">{content.categoryLabel || "Catégorie"}</p>
|
||||||
scrollToLibraryTool(anchorId, true);
|
<h2 id={getLibraryCategoryAnchorId(activeCategory)}>{activeCategory.title}</h2>
|
||||||
}}
|
{activeCategory.description && <p>{activeCategory.description}</p>}
|
||||||
>
|
</div>
|
||||||
<span className="module-icon" aria-hidden="true">
|
</div>
|
||||||
<span className={`module-icon-svg module-icon-${definition.icon}`} />
|
{activeCategory.modules.map((module) => (
|
||||||
</span>
|
<LibraryToolExample
|
||||||
<span>{title}</span>
|
key={module.id}
|
||||||
</a>
|
module={module}
|
||||||
);
|
sourceData={libraryPayload.modules?.[module.id] || {}}
|
||||||
})}
|
description={getToolDescription(siteContent, MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad)}
|
||||||
</div>
|
categoryTitle={activeCategory.title}
|
||||||
</nav>
|
docs={content.toolDocs?.[module.type]}
|
||||||
<section className="library-tools" aria-label={content.toolsLabel}>
|
featureHeading={content.featureHeading}
|
||||||
{libraryPayload.toolbox.modules.map((module) => (
|
controlLegendHeading={content.controlLegendHeading}
|
||||||
<LibraryToolExample
|
context={moduleContext}
|
||||||
key={module.id}
|
/>
|
||||||
module={module}
|
))}
|
||||||
sourceData={libraryPayload.modules?.[module.id] || {}}
|
</section>
|
||||||
description={getToolDescription(siteContent, MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad)}
|
)}
|
||||||
context={moduleContext}
|
</section>
|
||||||
/>
|
</div>
|
||||||
))}
|
|
||||||
</section>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LibraryToolExample({ module, sourceData, description, context }) {
|
function LibraryDocNav({ content, categories, activeCategoryKey, onSelectCategory }) {
|
||||||
|
return (
|
||||||
|
<nav className="library-doc-nav nebula-panel" aria-label={content.tocLabel || "Sommaire des outils"}>
|
||||||
|
<div className="library-toc-heading">
|
||||||
|
<h2>{content.docTocTitle || content.tocTitle || "Sommaire"}</h2>
|
||||||
|
<p>{content.tocDescription || "Accédez directement à un exemple d’outil dans la page."}</p>
|
||||||
|
</div>
|
||||||
|
<div className="library-doc-nav-groups">
|
||||||
|
{categories.map((category) => (
|
||||||
|
<div className={`library-doc-nav-group ${category.key === activeCategoryKey ? "active" : ""}`} key={category.key}>
|
||||||
|
<a href={`#/library#${getLibraryCategoryAnchorId(category)}`} onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onSelectCategory(category);
|
||||||
|
}}>{category.title}</a>
|
||||||
|
<ul hidden={category.key !== activeCategoryKey}>
|
||||||
|
{category.modules.map((module) => {
|
||||||
|
const definition = MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad;
|
||||||
|
const title = module.title || definition.label || "Outil";
|
||||||
|
const anchorId = getLibraryAnchorId(module);
|
||||||
|
return (
|
||||||
|
<li key={module.id}>
|
||||||
|
<a
|
||||||
|
href={`#/library#${anchorId}`}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onSelectCategory(category, anchorId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="module-icon" aria-hidden="true">
|
||||||
|
<span className={`module-icon-svg module-icon-${definition.icon}`} />
|
||||||
|
</span>
|
||||||
|
<span>{title}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LibraryToolExample({ module, sourceData, description, categoryTitle, docs, featureHeading, controlLegendHeading, context }) {
|
||||||
const definition = MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad;
|
const definition = MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad;
|
||||||
const Component = definition.Component;
|
const Component = definition.Component;
|
||||||
const [scrollable, setScrollable] = useState(false);
|
const [scrollable, setScrollable] = useState(false);
|
||||||
|
|
@ -259,55 +335,220 @@ function LibraryToolExample({ module, sourceData, description, context }) {
|
||||||
<section id={getLibraryAnchorId(module)} className="library-tool-section">
|
<section id={getLibraryAnchorId(module)} className="library-tool-section">
|
||||||
<div className="library-tool-intro">
|
<div className="library-tool-intro">
|
||||||
<div>
|
<div>
|
||||||
<h2>{title}</h2>
|
<div className="library-tool-title-row">
|
||||||
{description && <p>{description}</p>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<article className={`module library-tool-example ${scrollable ? "is-scrollable" : ""}`}>
|
|
||||||
<header>
|
|
||||||
<div>
|
|
||||||
<span className="module-icon" aria-hidden="true">
|
<span className="module-icon" aria-hidden="true">
|
||||||
<span className={`module-icon-svg module-icon-${definition.icon}`} />
|
<span className={`module-icon-svg module-icon-${definition.icon}`} />
|
||||||
</span>
|
</span>
|
||||||
|
<h2>{title}</h2>
|
||||||
|
</div>
|
||||||
|
<span className="library-tool-badge">{categoryTitle}</span>
|
||||||
|
{description && <p>{description}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`library-example-layout ${Array.isArray(docs?.controls) && docs.controls.length ? "has-legend" : ""}`}>
|
||||||
|
<ToolControlLegend items={docs?.controls} heading={controlLegendHeading || "Repères"} />
|
||||||
|
<article className={`module library-tool-example ${scrollable ? "is-scrollable" : ""}`}>
|
||||||
|
<header>
|
||||||
<div>
|
<div>
|
||||||
<h2>{definition.label}</h2>
|
<h2>{definition.label}</h2>
|
||||||
|
<p>{definition.mode}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="library-example-actions">
|
||||||
<div className="library-example-actions">
|
{definition.scrollable && (
|
||||||
{definition.scrollable && (
|
<button
|
||||||
<button
|
className={`module-scroll-button ${scrollable ? "active" : ""}`}
|
||||||
className={`module-scroll-button ${scrollable ? "active" : ""}`}
|
type="button"
|
||||||
type="button"
|
onClick={() => setScrollable((value) => !value)}
|
||||||
onClick={() => setScrollable((value) => !value)}
|
aria-label={`${scrollable ? "Désactiver" : "Activer"} le scroll de ${title}`}
|
||||||
aria-label={`${scrollable ? "Désactiver" : "Activer"} le scroll de ${title}`}
|
aria-pressed={scrollable}
|
||||||
aria-pressed={scrollable}
|
title={scrollable ? "Désactiver le scroll" : "Activer le scroll"}
|
||||||
title={scrollable ? "Désactiver le scroll" : "Activer le scroll"}
|
>
|
||||||
>
|
<Icon name="scrollable" />
|
||||||
<Icon name="scrollable" />
|
<i aria-hidden="true" />
|
||||||
<i aria-hidden="true" />
|
</button>
|
||||||
|
)}
|
||||||
|
{definition.editable && (
|
||||||
|
<button
|
||||||
|
className={`module-edit-button ${editing ? "active" : ""}`}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setEditing((value) => !value)}
|
||||||
|
aria-label={`${editing ? "Masquer" : "Afficher"} l'ajout de ${title}`}
|
||||||
|
aria-pressed={editing}
|
||||||
|
title={editing ? "Masquer l'ajout" : "Ajouter"}
|
||||||
|
>
|
||||||
|
<Icon name="add" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button className="library-reset-button" type="button" onClick={() => context.setModuleData(LIBRARY_TOOLBOX_ID, module.id, cloneData(sourceData))} aria-label={`Réinitialiser ${title}`} title="Réinitialiser l'exemple">
|
||||||
|
<Icon name="refresh" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
</div>
|
||||||
{definition.editable && (
|
</header>
|
||||||
<button
|
<div className={`module-content ${scrollable ? "is-scrollable legacy-scrollbar" : ""}`}>
|
||||||
className={`module-edit-button ${editing ? "active" : ""}`}
|
<Component toolboxId={LIBRARY_TOOLBOX_ID} moduleId={module.id} context={context} editing={editing} />
|
||||||
type="button"
|
|
||||||
onClick={() => setEditing((value) => !value)}
|
|
||||||
aria-label={`${editing ? "Masquer" : "Afficher"} l'ajout de ${title}`}
|
|
||||||
aria-pressed={editing}
|
|
||||||
title={editing ? "Masquer l'ajout" : "Ajouter"}
|
|
||||||
>
|
|
||||||
<Icon name="add" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button className="library-reset-button" type="button" onClick={() => context.setModuleData(LIBRARY_TOOLBOX_ID, module.id, cloneData(sourceData))} aria-label={`Réinitialiser ${title}`} title="Réinitialiser l'exemple">
|
|
||||||
<Icon name="refresh" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</article>
|
||||||
<div className={`module-content ${scrollable ? "is-scrollable legacy-scrollbar" : ""}`}>
|
</div>
|
||||||
<Component toolboxId={LIBRARY_TOOLBOX_ID} moduleId={module.id} context={context} editing={editing} />
|
<ToolFeatureList docs={docs} heading={featureHeading || "Fonctionnalités"} />
|
||||||
</div>
|
</section>
|
||||||
</article>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToolControlLegend({ items, heading }) {
|
||||||
|
const legendItems = Array.isArray(items) ? items : [];
|
||||||
|
if (!legendItems.length) return null;
|
||||||
|
const groups = legendItems.reduce((accumulator, item) => {
|
||||||
|
const groupTitle = item.group || "Contrôles";
|
||||||
|
const existingGroup = accumulator.find((group) => group.title === groupTitle);
|
||||||
|
if (existingGroup) existingGroup.items.push(item);
|
||||||
|
else accumulator.push({ title: groupTitle, items: [item] });
|
||||||
|
return accumulator;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className="library-control-legend" aria-label={heading}>
|
||||||
|
<h3>{heading}</h3>
|
||||||
|
{groups.map((group) => {
|
||||||
|
const genericSingleGroup = groups.length === 1 && group.title === "Contrôles";
|
||||||
|
return (
|
||||||
|
<section className={`library-control-legend-group ${genericSingleGroup ? "is-generic-single" : ""}`} key={group.title}>
|
||||||
|
{!genericSingleGroup && <h4>{group.title}</h4>}
|
||||||
|
<div className="library-control-legend-items">
|
||||||
|
{group.items.map((item) => (
|
||||||
|
<div className="library-control-legend-item" key={item.title}>
|
||||||
|
<ControlPreview item={item} />
|
||||||
|
<div>
|
||||||
|
<strong>{item.title}</strong>
|
||||||
|
{item.text && <p>{item.text}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ControlPreview({ item }) {
|
||||||
|
const preview = item.preview || {};
|
||||||
|
const kind = preview.kind || "iconButton";
|
||||||
|
|
||||||
|
if (kind === "textFormats") {
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview notepad-toolbar-group" aria-hidden="true">
|
||||||
|
<span className="notepad-toolbar-button">B</span>
|
||||||
|
<span className="notepad-toolbar-button"><em>I</em></span>
|
||||||
|
<span className="notepad-toolbar-button"><u>U</u></span>
|
||||||
|
<span className="notepad-toolbar-button"><s>S</s></span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "textButton") {
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview" aria-hidden="true">
|
||||||
|
<span className={`notepad-toolbar-button ${preview.active ? "active" : ""}`}>{preview.label || item.title.slice(0, 1)}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "listDropdown") {
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview" aria-hidden="true">
|
||||||
|
<span className="notepad-toolbar-button active"><span className="notepad-list-icon" /></span>
|
||||||
|
<span className="notepad-toolbar-button">•</span>
|
||||||
|
<span className="notepad-toolbar-button">1.</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "color") {
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview" aria-hidden="true">
|
||||||
|
<span className="notepad-color-toggle is-rainbow active" />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "drawingWidth") {
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview" aria-hidden="true">
|
||||||
|
<span className="drawing-width-toggle active"><span style={{ "--line-width": `${preview.width || 4}px` }} /></span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "switch") {
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview" aria-hidden="true">
|
||||||
|
<span className={`module-scroll-button drawing-storage-switch ${preview.active ? "active" : ""}`}>
|
||||||
|
<Icon name={preview.icon || item.icon || "settings"} />
|
||||||
|
<i />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "buttonGroup") {
|
||||||
|
const buttons = Array.isArray(preview.buttons) ? preview.buttons : [];
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview" aria-hidden="true">
|
||||||
|
{buttons.map((button, index) => (
|
||||||
|
<span className={`notepad-toolbar-button ${button.label && String(button.label).length > 2 ? "is-wide" : ""} ${button.active ? "active" : ""} ${button.danger ? "danger" : ""}`} key={`${button.label || button.icon || index}-${index}`}>
|
||||||
|
{button.icon ? <Icon name={button.icon} /> : button.label}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="library-control-preview" aria-hidden="true">
|
||||||
|
<span className={`notepad-toolbar-button ${preview.active ? "active" : ""} ${preview.danger ? "danger" : ""}`}>
|
||||||
|
<Icon name={preview.icon || item.icon || "settings"} />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToolFeatureList({ docs, heading }) {
|
||||||
|
const features = Array.isArray(docs?.features) ? docs.features : [];
|
||||||
|
if (!features.length && !docs?.importFormat) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="library-tool-docs">
|
||||||
|
<details className="library-tool-feature-list">
|
||||||
|
<summary>
|
||||||
|
<h3>{heading}</h3>
|
||||||
|
<Icon name="chevron-down" />
|
||||||
|
</summary>
|
||||||
|
<div className="library-tool-feature-content">
|
||||||
|
<dl>
|
||||||
|
{features.map((feature) => (
|
||||||
|
<div key={feature.title}>
|
||||||
|
<dt>{feature.title}</dt>
|
||||||
|
<dd>
|
||||||
|
<p>{feature.text}</p>
|
||||||
|
{Array.isArray(feature.details) && feature.details.length > 0 && (
|
||||||
|
<ul>
|
||||||
|
{feature.details.map((detail) => <li key={detail}>{detail}</li>)}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
{docs?.importFormat && (
|
||||||
|
<div className="library-tool-import-format">
|
||||||
|
<h3>{docs.importFormat.title}</h3>
|
||||||
|
<p>{docs.importFormat.text}</p>
|
||||||
|
<pre><code>{docs.importFormat.example}</code></pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,17 @@ span {
|
||||||
margin-top: var(--space-4);
|
margin-top: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-link-button {
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-link-button .ui-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
color: var(--color-accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
.hero-panel {
|
.hero-panel {
|
||||||
padding: var(--space-5);
|
padding: var(--space-5);
|
||||||
border: 1px solid rgba(165, 180, 252, 0.14);
|
border: 1px solid rgba(165, 180, 252, 0.14);
|
||||||
|
|
@ -249,9 +260,20 @@ span {
|
||||||
background: currentColor;
|
background: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-toc {
|
.library-doc-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-3);
|
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
|
||||||
|
align-items: start;
|
||||||
|
gap: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-doc-nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
display: grid;
|
||||||
|
max-height: calc(100vh - var(--space-10));
|
||||||
|
gap: var(--space-4);
|
||||||
|
overflow: auto;
|
||||||
padding: var(--space-4);
|
padding: var(--space-4);
|
||||||
scroll-margin-top: var(--space-5);
|
scroll-margin-top: var(--space-5);
|
||||||
}
|
}
|
||||||
|
|
@ -278,53 +300,91 @@ span {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-toc-links {
|
.library-doc-nav-groups {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
gap: var(--space-4);
|
||||||
gap: 8px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-toc-links a {
|
.library-doc-nav-group {
|
||||||
display: inline-flex;
|
display: grid;
|
||||||
min-width: 0;
|
|
||||||
min-height: 40px;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 5px 10px 5px 5px;
|
}
|
||||||
border: 1px solid rgba(150, 165, 205, 0.14);
|
|
||||||
border-radius: var(--radius-md);
|
.library-doc-nav-group > a {
|
||||||
background: rgba(5, 7, 17, 0.28);
|
color: var(--color-text-primary);
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-toc-links a:hover,
|
.library-doc-nav-group > a:hover,
|
||||||
.library-toc-links a:focus-visible {
|
.library-doc-nav-group > a:focus-visible {
|
||||||
border-color: rgba(246, 196, 83, 0.42);
|
color: var(--color-accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-doc-nav-group.active > a {
|
||||||
|
color: var(--color-accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-doc-nav-group ul {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-doc-nav-group li {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-doc-nav-group li a {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 28px minmax(0, 1fr);
|
||||||
|
min-height: 36px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 8px 4px 4px;
|
||||||
|
border: 1px solid rgba(150, 165, 205, 0.1);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: rgba(5, 7, 17, 0.22);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-doc-nav-group li a:hover,
|
||||||
|
.library-doc-nav-group li a:focus-visible {
|
||||||
|
border-color: rgba(246, 196, 83, 0.36);
|
||||||
background: rgba(246, 196, 83, 0.08);
|
background: rgba(246, 196, 83, 0.08);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-toc-links .module-icon {
|
.library-doc-nav-group .module-icon {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-toc-links .module-icon-svg {
|
.library-doc-nav-group .module-icon-svg {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-tools {
|
.library-tools {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-5);
|
gap: var(--space-6);
|
||||||
justify-items: stretch;
|
justify-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-tool-category {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-5);
|
||||||
|
min-width: 0;
|
||||||
|
scroll-margin-top: var(--space-8);
|
||||||
|
}
|
||||||
|
|
||||||
.library-tool-section {
|
.library-tool-section {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
@ -347,17 +407,75 @@ span {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-category-hero {
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: var(--space-6);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 92% 12%, rgba(246, 196, 83, 0.12), transparent 30%),
|
||||||
|
var(--gradient-nebula),
|
||||||
|
linear-gradient(135deg, rgba(16, 20, 38, 0.9), rgba(5, 7, 17, 0.88));
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
|
||||||
|
@include mixins.gold-panel-frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-category-hero > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-category-hero h2,
|
||||||
|
.library-category-hero p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-category-hero h2 {
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: clamp(1.7rem, 3vw, var(--font-size-3xl));
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-category-hero p:not(.eyebrow) {
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
.library-tool-intro {
|
.library-tool-intro {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-tool-intro h2,
|
.library-tool-title-row {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-3);
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-title-row .module-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-title-row .module-icon-svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-intro h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.library-tool-intro p {
|
.library-tool-intro p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.library-tool-intro h2 {
|
.library-tool-intro h2 {
|
||||||
margin-bottom: 6px;
|
|
||||||
font-size: var(--font-size-xl);
|
font-size: var(--font-size-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -365,6 +483,277 @@ span {
|
||||||
max-width: 860px;
|
max-width: 860px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-example-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
|
||||||
|
align-items: start;
|
||||||
|
justify-content: stretch;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-example-layout:not(.has-legend) {
|
||||||
|
grid-template-columns: minmax(0, 860px);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-docs {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend,
|
||||||
|
.library-tool-feature-list {
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid rgba(150, 165, 205, 0.12);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: rgba(5, 7, 17, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: var(--space-3);
|
||||||
|
border-color: rgba(246, 196, 83, 0.22);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 100% 0%, rgba(246, 196, 83, 0.08), transparent 34%),
|
||||||
|
rgba(5, 7, 17, 0.46);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend h3,
|
||||||
|
.library-tool-feature-list h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend-group {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-2);
|
||||||
|
min-width: 0;
|
||||||
|
padding: var(--space-2);
|
||||||
|
border: 1px solid rgba(150, 165, 205, 0.1);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: rgba(5, 7, 17, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend-group.is-generic-single {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend-group h4 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-accent-gold);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 900;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend-items {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(34px, max-content) minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview {
|
||||||
|
display: inline-flex;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 34px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview.notepad-toolbar-group {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding-right: 0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview-stack {
|
||||||
|
display: inline-grid;
|
||||||
|
gap: 6px;
|
||||||
|
justify-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview-row {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview .notepad-toolbar-button,
|
||||||
|
.library-control-preview .drawing-control-button,
|
||||||
|
.library-control-preview .notepad-color-toggle,
|
||||||
|
.library-control-preview .drawing-width-toggle,
|
||||||
|
.library-control-preview .module-scroll-button {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview .notepad-toolbar-button.is-wide {
|
||||||
|
width: auto;
|
||||||
|
min-width: 46px;
|
||||||
|
padding: 0 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview .notepad-color-toggle.active,
|
||||||
|
.library-control-preview .drawing-width-toggle.active {
|
||||||
|
border-color: rgba(246, 196, 83, 0.48);
|
||||||
|
background: rgba(246, 196, 83, 0.1);
|
||||||
|
color: var(--color-accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-preview .module-scroll-button {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend-item strong {
|
||||||
|
display: block;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-control-legend-item p {
|
||||||
|
margin: 3px 0 0;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list {
|
||||||
|
display: grid;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
border-color: rgba(246, 196, 83, 0.18);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(16, 20, 38, 0.52), rgba(5, 7, 17, 0.36)),
|
||||||
|
rgba(5, 7, 17, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: var(--space-4);
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list summary .ui-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: transform var(--duration-fast) var(--ease-standard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list[open] summary .ui-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-content {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-4);
|
||||||
|
padding: 0 var(--space-5) var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list dl {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-3);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list dl > div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(180px, 0.28fr) minmax(0, 1fr);
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: var(--space-3);
|
||||||
|
border: 1px solid rgba(150, 165, 205, 0.12);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: rgba(5, 7, 17, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list dt {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list dd {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list dd p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list dd ul {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
margin: var(--space-2) 0 0;
|
||||||
|
padding-left: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-feature-list dd li {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-import-format {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding-top: var(--space-3);
|
||||||
|
border-top: 1px solid rgba(246, 196, 83, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-import-format h3,
|
||||||
|
.library-tool-import-format p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-import-format pre {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--space-3);
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid rgba(150, 165, 205, 0.12);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: rgba(2, 3, 9, 0.42);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-tool-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
width: fit-content;
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
padding: 4px 9px;
|
||||||
|
border: 1px solid rgba(246, 196, 83, 0.24);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background: rgba(246, 196, 83, 0.08);
|
||||||
|
color: var(--color-accent-gold);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
.library-tool-example {
|
.library-tool-example {
|
||||||
width: min(100%, 860px);
|
width: min(100%, 860px);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,11 @@
|
||||||
-webkit-mask-image: url("/static/icons/add.svg");
|
-webkit-mask-image: url("/static/icons/add.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-icon-book {
|
||||||
|
mask-image: url("/static/icons/book.svg");
|
||||||
|
-webkit-mask-image: url("/static/icons/book.svg");
|
||||||
|
}
|
||||||
|
|
||||||
.ui-icon-copy {
|
.ui-icon-copy {
|
||||||
mask-image: url("/static/icons/copy.svg");
|
mask-image: url("/static/icons/copy.svg");
|
||||||
-webkit-mask-image: url("/static/icons/copy.svg");
|
-webkit-mask-image: url("/static/icons/copy.svg");
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-home-grid,
|
.game-home-grid,
|
||||||
.game-layout {
|
.game-layout,
|
||||||
|
.library-doc-layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,6 +189,16 @@
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-doc-nav {
|
||||||
|
position: static;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-example-layout,
|
||||||
|
.library-tool-feature-list dl > div {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.filter-options {
|
.filter-options {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,12 +145,17 @@
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-secondary-links {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-about-link {
|
.sidebar-about-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: auto;
|
|
||||||
padding: 7px 12px;
|
padding: 7px 12px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
|
|
@ -181,6 +186,12 @@
|
||||||
filter var(--duration-fast) var(--ease-standard);
|
filter var(--duration-fast) var(--ease-standard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-about-icon .ui-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-about-link:hover {
|
.sidebar-about-link:hover {
|
||||||
background: var(--color-bg-hover);
|
background: var(--color-bg-hover);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue