From 02cf7923245befa145948f668267a0b510edb1b7 Mon Sep 17 00:00:00 2001 From: mfagundez Date: Mon, 29 Dec 2025 17:18:57 -0300 Subject: [PATCH 01/20] feat(CodeComparison): add a new component for comparing code snippets with a draggable slider --- .../CodeComparison/CodeComparison.tsx | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 src/components/composites/CodeComparison/CodeComparison.tsx diff --git a/src/components/composites/CodeComparison/CodeComparison.tsx b/src/components/composites/CodeComparison/CodeComparison.tsx new file mode 100644 index 00000000..3581e629 --- /dev/null +++ b/src/components/composites/CodeComparison/CodeComparison.tsx @@ -0,0 +1,216 @@ +import React, { useState, useRef, useEffect, useCallback } from "react"; +import { Preview } from "../Preview/Preview"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism"; + +interface CodeComparisonProps { + beforeHtml: string; + afterHtml: string; + beforeLabel?: string; + afterLabel?: string; + defaultPosition?: number; // 0-100, default 50 + height?: string; // CSS height value, default "600px" +} + +export const CodeComparison: React.FC = ({ + beforeHtml, + afterHtml, + beforeLabel = "Before", + afterLabel = "After", + defaultPosition = 50, + height = "600px", +}) => { + const [sliderPosition, setSliderPosition] = useState(defaultPosition); + const [isDragging, setIsDragging] = useState(false); + const [showBeforeCode, setShowBeforeCode] = useState(false); + const [showAfterCode, setShowAfterCode] = useState(false); + const containerRef = useRef(null); + + const updateSliderPosition = useCallback((clientX: number) => { + if (!containerRef.current) return; + + const rect = containerRef.current.getBoundingClientRect(); + const x = clientX - rect.left; + const percentage = (x / rect.width) * 100; + + // Limit between 0% and 100% + setSliderPosition(Math.min(Math.max(percentage, 0), 100)); + }, []); + + const handleMouseDown = () => { + setIsDragging(true); + }; + + const handleMouseUp = useCallback(() => { + setIsDragging(false); + }, []); + + const handleMouseMove = useCallback((e: MouseEvent) => { + e.preventDefault(); + updateSliderPosition(e.clientX); + }, [updateSliderPosition]); + + const handleTouchStart = () => { + setIsDragging(true); + }; + + const handleTouchEnd = () => { + setIsDragging(false); + }; + + const handleTouchMove = (e: React.TouchEvent) => { + const touch = e.touches[0]; + updateSliderPosition(touch.clientX); + }; + + // Attach global mouse event listeners when dragging + useEffect(() => { + if (isDragging) { + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + + // Prevent text selection while dragging + document.body.style.userSelect = "none"; + document.body.style.cursor = "col-resize"; + } else { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + + // Restore normal selection and cursor + document.body.style.userSelect = ""; + document.body.style.cursor = ""; + } + + // Cleanup on unmount + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.userSelect = ""; + document.body.style.cursor = ""; + }; + }, [isDragging, handleMouseMove, handleMouseUp]); + + return ( +
+ {/* Background Layer (After) - Always full width */} +
+
+ {afterLabel && ( +
+ {afterLabel} +
+ )} + +
+ + {showAfterCode ? ( +
+ + {afterHtml} + +
+ ) : ( + {}} + useIframe={true} + /> + )} +
+ + {/* Foreground Layer (Before) - Clipped by slider position */} +
+
+ {beforeLabel && ( +
+ {beforeLabel} +
+ )} + +
+ + {showBeforeCode ? ( +
+ + {beforeHtml} + +
+ ) : ( + {}} + useIframe={true} + /> + )} +
+ + {/* Slider Divider */} +
+
+ + + + +
+
+
+ ); +}; + From 6deb5c7f71f053ada872614299ae58b8c0e17022 Mon Sep 17 00:00:00 2001 From: mfagundez Date: Mon, 29 Dec 2025 18:16:40 -0300 Subject: [PATCH 02/20] refactor(Comparison): extract types and refactor component structure - Extract types to separate types.ts file for better organization - Refactor component from CodeComparison to Comparison - Implement flexible content type system (html, text, code, custom) - Add sync modes functionality (default: true) - Replace beforeHtml/afterHtml props with before/after objects --- .../Comparison.tsx} | 250 ++++++++++++------ src/components/composites/Comparison/types.ts | 24 ++ 2 files changed, 189 insertions(+), 85 deletions(-) rename src/components/composites/{CodeComparison/CodeComparison.tsx => Comparison/Comparison.tsx} (50%) create mode 100644 src/components/composites/Comparison/types.ts diff --git a/src/components/composites/CodeComparison/CodeComparison.tsx b/src/components/composites/Comparison/Comparison.tsx similarity index 50% rename from src/components/composites/CodeComparison/CodeComparison.tsx rename to src/components/composites/Comparison/Comparison.tsx index 3581e629..84ad7b5b 100644 --- a/src/components/composites/CodeComparison/CodeComparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -2,30 +2,111 @@ import React, { useState, useRef, useEffect, useCallback } from "react"; import { Preview } from "../Preview/Preview"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism"; +import type { ComparisonProps, ComparisonItem, ContentMode, ContentType } from "./types"; -interface CodeComparisonProps { - beforeHtml: string; - afterHtml: string; - beforeLabel?: string; - afterLabel?: string; - defaultPosition?: number; // 0-100, default 50 - height?: string; // CSS height value, default "600px" -} - -export const CodeComparison: React.FC = ({ - beforeHtml, - afterHtml, - beforeLabel = "Before", - afterLabel = "After", +// Internal component to render content based on type and mode +const ContentRenderer: React.FC<{ + item: ComparisonItem; + currentMode: ContentMode; +}> = ({ item, currentMode }) => { + // If custom render is provided, use it + if (item.customRender) { + return <>{item.customRender(item.content, currentMode)}; + } + + // Handle different content types + switch (item.type) { + case "html": + if (currentMode === "rendered") { + return ( + {}} useIframe={true} /> + ); + } else { + return ( +
+ + {String(item.content)} + +
+ ); + } + + case "text": + return ( +
+
+            {String(item.content)}
+          
+
+ ); + + case "code": + return ( +
+ + {String(item.content)} + +
+ ); + + default: + return ( +
+ Unknown content type: {item.type} +
+ ); + } +}; + +export const Comparison: React.FC = ({ + before, + after, defaultPosition = 50, height = "600px", + syncModes = true, }) => { const [sliderPosition, setSliderPosition] = useState(defaultPosition); const [isDragging, setIsDragging] = useState(false); - const [showBeforeCode, setShowBeforeCode] = useState(false); - const [showAfterCode, setShowAfterCode] = useState(false); + + // Mode states - if sync is true, we only need one state + const [beforeMode, setBeforeMode] = useState("rendered"); + const [afterMode, setAfterMode] = useState("rendered"); + const containerRef = useRef(null); + // Default available modes based on content type + const getDefaultModes = (type: ContentType): ContentMode[] => { + switch (type) { + case "html": + return ["rendered", "raw"]; + case "text": + return ["raw"]; + case "code": + return ["raw"]; + default: + return ["rendered"]; + } + }; + + const beforeModes = before.availableModes || getDefaultModes(before.type); + const afterModes = after.availableModes || getDefaultModes(after.type); + const updateSliderPosition = useCallback((clientX: number) => { if (!containerRef.current) return; @@ -45,10 +126,13 @@ export const CodeComparison: React.FC = ({ setIsDragging(false); }, []); - const handleMouseMove = useCallback((e: MouseEvent) => { - e.preventDefault(); - updateSliderPosition(e.clientX); - }, [updateSliderPosition]); + const handleMouseMove = useCallback( + (e: MouseEvent) => { + e.preventDefault(); + updateSliderPosition(e.clientX); + }, + [updateSliderPosition] + ); const handleTouchStart = () => { setIsDragging(true); @@ -63,19 +147,42 @@ export const CodeComparison: React.FC = ({ updateSliderPosition(touch.clientX); }; + // Toggle mode handlers + const toggleBeforeMode = () => { + const currentIndex = beforeModes.indexOf(beforeMode); + const nextIndex = (currentIndex + 1) % beforeModes.length; + const nextMode = beforeModes[nextIndex]; + + setBeforeMode(nextMode); + if (syncModes) { + setAfterMode(nextMode); + } + }; + + const toggleAfterMode = () => { + const currentIndex = afterModes.indexOf(afterMode); + const nextIndex = (currentIndex + 1) % afterModes.length; + const nextMode = afterModes[nextIndex]; + + setAfterMode(nextMode); + if (syncModes) { + setBeforeMode(nextMode); + } + }; + // Attach global mouse event listeners when dragging useEffect(() => { if (isDragging) { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); - + // Prevent text selection while dragging document.body.style.userSelect = "none"; document.body.style.cursor = "col-resize"; } else { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); - + // Restore normal selection and cursor document.body.style.userSelect = ""; document.body.style.cursor = ""; @@ -90,6 +197,15 @@ export const CodeComparison: React.FC = ({ }; }, [isDragging, handleMouseMove, handleMouseUp]); + // Get mode label for button + const getModeLabel = (mode: ContentMode) => { + return mode === "rendered" ? "👁️ Vista" : "📝 Código"; + }; + + const getModeTitle = (mode: ContentMode) => { + return mode === "rendered" ? "Ver Código" : "Ver Renderizado"; + }; + return (
= ({ onTouchEnd={handleTouchEnd} > {/* Background Layer (After) - Always full width */} -
- {afterLabel && ( + {after.label && (
- {afterLabel} + {after.label}
)} - -
- - {showAfterCode ? ( -
- 1 && ( +
- ) : ( - {}} - useIframe={true} - /> - )} + {getModeLabel(afterMode)} + + )} +
+ +
{/* Foreground Layer (Before) - Clipped by slider position */} @@ -150,41 +248,23 @@ export const CodeComparison: React.FC = ({ }} >
- {beforeLabel && ( + {before.label && (
- {beforeLabel} + {before.label}
)} - -
- - {showBeforeCode ? ( -
- 1 && ( +
- ) : ( - {}} - useIframe={true} - /> - )} + {getModeLabel(beforeMode)} + + )} + + + {/* Slider Divider */} diff --git a/src/components/composites/Comparison/types.ts b/src/components/composites/Comparison/types.ts new file mode 100644 index 00000000..b5033220 --- /dev/null +++ b/src/components/composites/Comparison/types.ts @@ -0,0 +1,24 @@ +import React from "react"; + +// Types for content modes +export type ContentMode = "rendered" | "raw"; + +export type ContentType = "html" | "text" | "code" | "custom"; + +export interface ComparisonItem { + content: string | unknown; + type: ContentType; + label?: string; + language?: string; // For code highlighting + availableModes?: ContentMode[]; // Modes available for this item + customRender?: (content: unknown, mode: ContentMode) => React.ReactNode; +} + +export interface ComparisonProps { + before: ComparisonItem; + after: ComparisonItem; + defaultPosition?: number; // 0-100, default 50 + height?: string; // CSS height value, default "600px" + syncModes?: boolean; // If true, both panels change mode together (default: true) +} + From ca47ac2123ec8c7324f8075cf945a1a4e3d569dc Mon Sep 17 00:00:00 2001 From: mfagundez Date: Mon, 29 Dec 2025 18:57:49 -0300 Subject: [PATCH 03/20] feat(Comparison): add dynamic layout selection - Add automatic layout detection (slider for visual content, side-by-side for text/code) - Implement side-by-side layout with responsive behavior - Add max-height constraints for mobile (90vh container, 50% per panel) - Ensure scroll behavior in both desktop and mobile views - Update types to include ComparisonLayout enum --- .../composites/Comparison/Comparison.tsx | 153 ++++++++++++++++-- src/components/composites/Comparison/types.ts | 6 +- 2 files changed, 148 insertions(+), 11 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 84ad7b5b..03b9aec0 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect, useCallback } from "react"; import { Preview } from "../Preview/Preview"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism"; -import type { ComparisonProps, ComparisonItem, ContentMode, ContentType } from "./types"; +import type { ComparisonProps, ComparisonItem, ContentMode, ContentType, ComparisonLayout } from "./types"; // Internal component to render content based on type and mode const ContentRenderer: React.FC<{ @@ -74,7 +74,19 @@ const ContentRenderer: React.FC<{ } }; -export const Comparison: React.FC = ({ +// Determine default layout based on content types +const determineLayout = (before: ComparisonItem, after: ComparisonItem): ComparisonLayout => { + // Use slider for visual content (HTML in rendered mode) + if (before.type === "html" || after.type === "html") { + return "slider"; + } + + // Use side-by-side for text and code + return "side-by-side"; +}; + +// Slider Comparison Component (original behavior) +const SliderComparison: React.FC> = ({ before, after, defaultPosition = 50, @@ -84,7 +96,7 @@ export const Comparison: React.FC = ({ const [sliderPosition, setSliderPosition] = useState(defaultPosition); const [isDragging, setIsDragging] = useState(false); - // Mode states - if sync is true, we only need one state + // Mode states const [beforeMode, setBeforeMode] = useState("rendered"); const [afterMode, setAfterMode] = useState("rendered"); @@ -114,7 +126,6 @@ export const Comparison: React.FC = ({ const x = clientX - rect.left; const percentage = (x / rect.width) * 100; - // Limit between 0% and 100% setSliderPosition(Math.min(Math.max(percentage, 0), 100)); }, []); @@ -176,19 +187,16 @@ export const Comparison: React.FC = ({ document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); - // Prevent text selection while dragging document.body.style.userSelect = "none"; document.body.style.cursor = "col-resize"; } else { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); - // Restore normal selection and cursor document.body.style.userSelect = ""; document.body.style.cursor = ""; } - // Cleanup on unmount return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); @@ -197,7 +205,6 @@ export const Comparison: React.FC = ({ }; }, [isDragging, handleMouseMove, handleMouseUp]); - // Get mode label for button const getModeLabel = (mode: ContentMode) => { return mode === "rendered" ? "👁️ Vista" : "📝 Código"; }; @@ -214,7 +221,7 @@ export const Comparison: React.FC = ({ onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} > - {/* Background Layer (After) - Always full width */} + {/* Background Layer (After) */}
= ({
- {/* Foreground Layer (Before) - Clipped by slider position */} + {/* Foreground Layer (Before) - Clipped */}
= ({ ); }; +// Side-by-Side Comparison Component (new) +const SideBySideComparison: React.FC> = ({ + before, + after, + height = "600px", + syncModes = true, +}) => { + // Mode states + const [beforeMode, setBeforeMode] = useState("rendered"); + const [afterMode, setAfterMode] = useState("rendered"); + + // Default available modes based on content type + const getDefaultModes = (type: ContentType): ContentMode[] => { + switch (type) { + case "html": + return ["rendered", "raw"]; + case "text": + return ["raw"]; + case "code": + return ["raw"]; + default: + return ["rendered"]; + } + }; + + const beforeModes = before.availableModes || getDefaultModes(before.type); + const afterModes = after.availableModes || getDefaultModes(after.type); + + // Toggle mode handlers + const toggleBeforeMode = () => { + const currentIndex = beforeModes.indexOf(beforeMode); + const nextIndex = (currentIndex + 1) % beforeModes.length; + const nextMode = beforeModes[nextIndex]; + + setBeforeMode(nextMode); + if (syncModes) { + setAfterMode(nextMode); + } + }; + + const toggleAfterMode = () => { + const currentIndex = afterModes.indexOf(afterMode); + const nextIndex = (currentIndex + 1) % afterModes.length; + const nextMode = afterModes[nextIndex]; + + setAfterMode(nextMode); + if (syncModes) { + setBeforeMode(nextMode); + } + }; + + const getModeLabel = (mode: ContentMode) => { + return mode === "rendered" ? "👁️ Vista" : "📝 Código"; + }; + + const getModeTitle = (mode: ContentMode) => { + return mode === "rendered" ? "Ver Código" : "Ver Renderizado"; + }; + + return ( +
+
+ {/* Before Panel */} +
+
+ {before.label && ( +
+ {before.label} +
+ )} + {beforeModes.length > 1 && ( + + )} +
+ +
+ + {/* After Panel */} +
+
+ {after.label && ( +
+ {after.label} +
+ )} + {afterModes.length > 1 && ( + + )} +
+ +
+
+
+ ); +}; + +// Main Comparison Component +export const Comparison: React.FC = (props) => { + const { before, after, layout } = props; + + // Determine layout automatically if not specified + const effectiveLayout = layout || determineLayout(before, after); + + // Render the appropriate layout + if (effectiveLayout === "slider") { + return ; + } else { + return ; + } +}; + diff --git a/src/components/composites/Comparison/types.ts b/src/components/composites/Comparison/types.ts index b5033220..020a4ca4 100644 --- a/src/components/composites/Comparison/types.ts +++ b/src/components/composites/Comparison/types.ts @@ -5,6 +5,9 @@ export type ContentMode = "rendered" | "raw"; export type ContentType = "html" | "text" | "code" | "custom"; +// Layout types for comparison +export type ComparisonLayout = "slider" | "side-by-side"; + export interface ComparisonItem { content: string | unknown; type: ContentType; @@ -17,8 +20,9 @@ export interface ComparisonItem { export interface ComparisonProps { before: ComparisonItem; after: ComparisonItem; - defaultPosition?: number; // 0-100, default 50 + defaultPosition?: number; // 0-100, default 50 (only for slider layout) height?: string; // CSS height value, default "600px" syncModes?: boolean; // If true, both panels change mode together (default: true) + layout?: ComparisonLayout; // Layout type, auto-determined if not specified } From b5c54dc713031fe8283a17222a6a7318cf0ff1dd Mon Sep 17 00:00:00 2001 From: mfagundez Date: Mon, 29 Dec 2025 19:55:28 -0300 Subject: [PATCH 04/20] feat(Comparison): add Mermaid and Markdown rendering support - Support mermaid diagrams with code/render toggle - Support markdown with GFM and math rendering - Reuse existing MermaidRenderer component --- .../composites/Comparison/Comparison.tsx | 70 ++++++++++++++++++- src/components/composites/Comparison/types.ts | 2 +- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 03b9aec0..2322792d 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -2,6 +2,11 @@ import React, { useState, useRef, useEffect, useCallback } from "react"; import { Preview } from "../Preview/Preview"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism"; +import MermaidRenderer from "../MermaidRenderer/MermaidRenderer"; +import Markdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import remarkMath from "remark-math"; +import rehypeKatex from "rehype-katex"; import type { ComparisonProps, ComparisonItem, ContentMode, ContentType, ComparisonLayout } from "./types"; // Internal component to render content based on type and mode @@ -65,6 +70,61 @@ const ContentRenderer: React.FC<{
); + case "mermaid": + if (currentMode === "rendered") { + return ( +
+ +
+ ); + } else { + return ( +
+ + {String(item.content)} + +
+ ); + } + + case "markdown": + if (currentMode === "rendered") { + return ( +
+ + {String(item.content)} + +
+ ); + } else { + return ( +
+ + {String(item.content)} + +
+ ); + } + default: return (
@@ -81,7 +141,7 @@ const determineLayout = (before: ComparisonItem, after: ComparisonItem): Compari return "slider"; } - // Use side-by-side for text and code + // Use side-by-side for text, code, mermaid, and markdown return "side-by-side"; }; @@ -107,6 +167,10 @@ const SliderComparison: React.FC> = ({ switch (type) { case "html": return ["rendered", "raw"]; + case "mermaid": + return ["rendered", "raw"]; + case "markdown": + return ["rendered", "raw"]; case "text": return ["raw"]; case "code": @@ -317,6 +381,10 @@ const SideBySideComparison: React.FC Date: Mon, 29 Dec 2025 20:11:19 -0300 Subject: [PATCH 05/20] feat(Comparison): add image and custom content rendering - Support image comparisons with slider layout - Add customRender prop for fully customizable content rendering --- .../composites/Comparison/Comparison.tsx | 20 +++++++++++++++++-- src/components/composites/Comparison/types.ts | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 2322792d..79bfe209 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -125,6 +125,17 @@ const ContentRenderer: React.FC<{ ); } + case "image": + return ( +
+ {item.label +
+ ); + default: return (
@@ -136,8 +147,9 @@ const ContentRenderer: React.FC<{ // Determine default layout based on content types const determineLayout = (before: ComparisonItem, after: ComparisonItem): ComparisonLayout => { - // Use slider for visual content (HTML in rendered mode) - if (before.type === "html" || after.type === "html") { + // Use slider for visual content (HTML and images) + if (before.type === "html" || after.type === "html" || + before.type === "image" || after.type === "image") { return "slider"; } @@ -171,6 +183,8 @@ const SliderComparison: React.FC> = ({ return ["rendered", "raw"]; case "markdown": return ["rendered", "raw"]; + case "image": + return ["rendered"]; case "text": return ["raw"]; case "code": @@ -385,6 +399,8 @@ const SideBySideComparison: React.FC Date: Mon, 29 Dec 2025 20:46:31 -0300 Subject: [PATCH 06/20] feat(Markdowner): integrate Comparison component as explanatory custom component - Add ComparisonRenderer component to parse comparison markdown syntax - Support syntax: type="..." with ---SEPARATOR--- divider - Integrate with Markdowner CustomCodeBlock for language="comparison" - Add CreatorWrapper support for creator mode - Document component in explanatory_components.yml with examples - Support all content types: html, code, text, mermaid, markdown, image - Auto-detect layout (slider for visual, side-by-side for text/code) --- docs/explanatory_components.yml | 102 ++++++++++++++++++ .../ComparisonRenderer/ComparisonRenderer.tsx | 86 +++++++++++++++ .../composites/Markdowner/Markdowner.tsx | 26 +++++ 3 files changed, 214 insertions(+) create mode 100644 src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx diff --git a/docs/explanatory_components.yml b/docs/explanatory_components.yml index 4abe14c5..76a17956 100644 --- a/docs/explanatory_components.yml +++ b/docs/explanatory_components.yml @@ -45,6 +45,108 @@ components: [Ask Rigo how to create a function in Python](https://4geeks.com/ask?query=help-me-understand-how-to-create-a-function-in-python-in-the-simplest-possible-way) + - name: comparison + intendedUse: + - explanation + - educational comparison + - code improvement demonstration + - before/after visualization + description: >- + The comparison component allows side-by-side or slider-based comparison of two pieces of content. + It supports multiple content types including HTML previews, code with syntax highlighting, text, + Mermaid diagrams, Markdown rendering, and images. Perfect for showing code improvements, design changes, + or comparing different approaches to solve the same problem. The component automatically selects the best + layout based on content type: slider for visual content (HTML, images) and side-by-side for textual/code content. + goodFor: + - showing code before and after refactoring or bug fixes + - comparing correct vs incorrect implementations + - demonstrating design improvements (images, HTML) + - comparing simple vs detailed prompts or explanations + - showing Mermaid diagram code vs rendered output + - comparing different algorithms or approaches side by side + - visual before/after demonstrations of UI changes + - comparing code with different levels of complexity or optimization + whenToUse: Use when showing two versions of something enhances learning - comparing approaches, demonstrating improvements, or showing the evolution from a problem to its solution. Particularly effective for code quality improvements, bug fixes, design iterations, or when teaching best practices by contrasting good and bad examples. + avoid: + - comparing more than 2 items (component only supports 2 panels) + - using for unrelated content that doesn't benefit from comparison + - overusing in a single lesson (can be overwhelming, use sparingly) + - very long code blocks that make scrolling difficult (keep comparisons focused) + - using when a simple explanation would be clearer + rules: + - CRITICAL - Must use exactly the text "---SEPARATOR---" on its own line to divide the two content blocks + - The type attribute is REQUIRED and must be one of these values - html, text, code, mermaid, markdown, image + - For type="code", you MUST also specify the language attribute (e.g. language="python", language="javascript") + - Both content blocks should be comparable in length and complexity for best visual results + - Use descriptive left and right labels to guide the student and provide context + - For images, use full URLs or paths starting with ./.learn/assets/ + - Keep the compared content focused on a single concept or improvement + - The separator must be on its own line with no extra spaces or characters + metadata: + - type - Content type (REQUIRED) - Must be one of html, text, code, mermaid, markdown, image + - language - Programming language (REQUIRED only for type="code") - python, javascript, java, etc + - left - Label for left panel (optional but recommended) - Describes what the left content represents + - right - Label for right panel (optional but recommended) - Describes what the right content represents + - layout - Force specific layout (optional) - "slider" for overlay comparison or "side-by-side" for split view. Auto-detected if omitted + - height - Component height (optional) - Default is "600px", accepts CSS values like "500px", "80vh", "700px" + - syncModes - Sync render/code toggle between panels (optional) - "true" or "false", default is "true" + example: | + ```comparison type="html" left="Sin estilos" right="Con estilos CSS" +

Título básico

+

Párrafo sin formato

+ + ---SEPARATOR--- + + +

Título estilizado

+

Párrafo con formato mejorado

+ ``` + --- Second Example --- + + ```comparison type="code" language="python" left="Código ineficiente" right="Código optimizado" + def find_max(numbers): + sorted_numbers = sorted(numbers) + return sorted_numbers[-1] + + ---SEPARATOR--- + + def find_max(numbers): + return max(numbers) + ``` + --- Third Example --- + + ```comparison type="text" left="Prompt vago" right="Prompt específico" height="400px" + Explica las funciones en Python + + ---SEPARATOR--- + + Explica las funciones en Python de forma clara: + - Sintaxis para definir funciones + - Cómo usar parámetros y argumentos + - Valores de retorno con return + - Ejemplos prácticos aplicados + ``` + --- Fourth Example --- + + ```comparison type="code" language="javascript" left="Versión con bucle" right="Versión con método" + function sumArray(arr) { + let sum = 0; + for(let i = 0; i < arr.length; i++) { + sum = sum + arr[i]; + } + return sum; + } + + ---SEPARATOR--- + + function sumArray(arr) { + return arr.reduce((sum, num) => sum + num, 0); + } + ``` + - name: image intendedUse: - educational explanation diff --git a/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx new file mode 100644 index 00000000..ac00db77 --- /dev/null +++ b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { Comparison } from "../Comparison/Comparison"; +import type { ComparisonItem, ContentType, ComparisonLayout } from "../Comparison/types"; +import { TMetadata } from "../Markdowner/types"; + +interface ComparisonRendererProps { + code: string; + metadata: TMetadata; + wholeMD: string; + node: any; + allowCreate: boolean; +} + +export const ComparisonRenderer: React.FC = ({ + code, + metadata, +}) => { + // Split content by separator + const parts = code.split("---SEPARATOR---"); + + if (parts.length !== 2) { + return ( +
+ Error: El componente Comparison requiere exactamente 2 bloques de contenido separados por "---SEPARATOR---" +
+ ); + } + + const [leftContent, rightContent] = parts.map(p => p.trim()); + + // Extract and validate metadata + const type = (metadata.type as string || "code") as ContentType; + const language = metadata.language as string | undefined; + const leftLabel = metadata.left as string | undefined; + const rightLabel = metadata.right as string | undefined; + const layout = metadata.layout as ComparisonLayout | undefined; + const height = (metadata.height as string) || "600px"; + const syncModes = metadata.syncModes !== "false" && metadata.syncModes !== false; // default true + + // Validate type + const validTypes: ContentType[] = ["html", "text", "code", "mermaid", "markdown", "image", "custom"]; + if (!validTypes.includes(type)) { + return ( +
+ Error: El tipo "{type}" no es válido. Usa: html, text, code, mermaid, markdown, image. +
+ ); + } + + // Validate code type requires language + if (type === "code" && !language) { + return ( +
+ Error: El tipo "code" requiere el atributo "language" (ej: language="python") +
+ ); + } + + // Build comparison items + const before: ComparisonItem = { + content: leftContent, + type: type, + label: leftLabel, + language: language, + }; + + const after: ComparisonItem = { + content: rightContent, + type: type, + label: rightLabel, + language: language, + }; + + return ( +
+ +
+ ); +}; + diff --git a/src/components/composites/Markdowner/Markdowner.tsx b/src/components/composites/Markdowner/Markdowner.tsx index 06753543..d2c50013 100644 --- a/src/components/composites/Markdowner/Markdowner.tsx +++ b/src/components/composites/Markdowner/Markdowner.tsx @@ -42,6 +42,7 @@ import { isRunnableCodeBlock } from "../../../utils/runnableDetection"; import MonacoEditor from "@monaco-editor/react"; import { Toolbar } from "../Editor/Editor"; import { eventBus } from "@/managers/eventBus"; +import { ComparisonRenderer } from "../ComparisonRenderer/ComparisonRenderer"; const ClickMeToGetID = ({ id }: { id: string }) => { @@ -725,6 +726,31 @@ const CustomCodeBlock = ({ return ; } + if (language === "comparison") { + if (isCreator && mode === "creator" && allowCreate) { + return ( + + + + ); + } + return ( + + ); + } + if (language === "changesDiff") { return ; } From 283681a43211bed181c06e2c0bbe8f0c685971f3 Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 11:21:51 -0300 Subject: [PATCH 07/20] feat(comparison): add defaultMode support for independent panel initial states Allow specifying initial display mode (raw/rendered) per panel via leftDefaultMode and rightDefaultMode metadata attributes. Enables showing code vs rendered output side-by-side with independent mode toggling. - Add defaultMode to ComparisonItem interface - Implement getInitialMode() helper function - Update ComparisonRenderer to parse leftDefaultMode/rightDefaultMode - Update documentation and examples --- docs/explanatory_components.yml | 26 +++++- .../composites/Comparison/Comparison.tsx | 84 +++++++++---------- src/components/composites/Comparison/types.ts | 1 + .../ComparisonRenderer/ComparisonRenderer.tsx | 6 +- 4 files changed, 69 insertions(+), 48 deletions(-) diff --git a/docs/explanatory_components.yml b/docs/explanatory_components.yml index 76a17956..28291195 100644 --- a/docs/explanatory_components.yml +++ b/docs/explanatory_components.yml @@ -62,7 +62,8 @@ components: - comparing correct vs incorrect implementations - demonstrating design improvements (images, HTML) - comparing simple vs detailed prompts or explanations - - showing Mermaid diagram code vs rendered output + - showing Mermaid diagram code vs rendered output (using leftDefaultMode="raw" and rightDefaultMode="rendered") + - showing Markdown source vs rendered output (using different defaultMode for each panel) - comparing different algorithms or approaches side by side - visual before/after demonstrations of UI changes - comparing code with different levels of complexity or optimization @@ -82,6 +83,8 @@ components: - For images, use full URLs or paths starting with ./.learn/assets/ - Keep the compared content focused on a single concept or improvement - The separator must be on its own line with no extra spaces or characters + - To show code vs rendered output, use leftDefaultMode="raw" and rightDefaultMode="rendered" with syncModes="false" + - When using different defaultModes, consider setting syncModes="false" so each panel can be toggled independently metadata: - type - Content type (REQUIRED) - Must be one of html, text, code, mermaid, markdown, image - language - Programming language (REQUIRED only for type="code") - python, javascript, java, etc @@ -90,6 +93,8 @@ components: - layout - Force specific layout (optional) - "slider" for overlay comparison or "side-by-side" for split view. Auto-detected if omitted - height - Component height (optional) - Default is "600px", accepts CSS values like "500px", "80vh", "700px" - syncModes - Sync render/code toggle between panels (optional) - "true" or "false", default is "true" + - leftDefaultMode - Initial display mode for left panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type + - rightDefaultMode - Initial display mode for right panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type example: | ```comparison type="html" left="Sin estilos" right="Con estilos CSS"

Título básico

@@ -146,6 +151,25 @@ components: return arr.reduce((sum, num) => sum + num, 0); } ``` + --- Fifth Example (with different initial modes) --- + + ```comparison type="mermaid" left="Mermaid Code" right="Rendered Diagram" syncModes="false" leftDefaultMode="raw" rightDefaultMode="rendered" + graph TD + A[Start] --> B{Decision} + B -->|Yes| C[Action 1] + B -->|No| D[Action 2] + C --> E[End] + D --> E + + ---SEPARATOR--- + + graph TD + A[Start] --> B{Decision} + B -->|Yes| C[Action 1] + B -->|No| D[Action 2] + C --> E[End] + D --> E + ``` - name: image intendedUse: diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 79bfe209..79eb378e 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -157,6 +157,38 @@ const determineLayout = (before: ComparisonItem, after: ComparisonItem): Compari return "side-by-side"; }; +// Helper function to get default modes based on content type +const getDefaultModes = (type: ContentType): ContentMode[] => { + switch (type) { + case "html": + return ["rendered", "raw"]; + case "mermaid": + return ["rendered", "raw"]; + case "markdown": + return ["rendered", "raw"]; + case "image": + return ["rendered"]; + case "text": + return ["raw"]; + case "code": + return ["raw"]; + default: + return ["rendered"]; + } +}; + +// Helper function to determine initial mode for an item +const getInitialMode = (item: ComparisonItem): ContentMode => { + // Use explicit defaultMode if provided + if (item.defaultMode) { + return item.defaultMode; + } + + // Otherwise use the first available mode + const modes = item.availableModes || getDefaultModes(item.type); + return modes[0]; +}; + // Slider Comparison Component (original behavior) const SliderComparison: React.FC> = ({ before, @@ -168,32 +200,12 @@ const SliderComparison: React.FC> = ({ const [sliderPosition, setSliderPosition] = useState(defaultPosition); const [isDragging, setIsDragging] = useState(false); - // Mode states - const [beforeMode, setBeforeMode] = useState("rendered"); - const [afterMode, setAfterMode] = useState("rendered"); + // Mode states - use defaultMode if provided, otherwise use first available mode + const [beforeMode, setBeforeMode] = useState(() => getInitialMode(before)); + const [afterMode, setAfterMode] = useState(() => getInitialMode(after)); const containerRef = useRef(null); - // Default available modes based on content type - const getDefaultModes = (type: ContentType): ContentMode[] => { - switch (type) { - case "html": - return ["rendered", "raw"]; - case "mermaid": - return ["rendered", "raw"]; - case "markdown": - return ["rendered", "raw"]; - case "image": - return ["rendered"]; - case "text": - return ["raw"]; - case "code": - return ["raw"]; - default: - return ["rendered"]; - } - }; - const beforeModes = before.availableModes || getDefaultModes(before.type); const afterModes = after.availableModes || getDefaultModes(after.type); @@ -386,29 +398,9 @@ const SideBySideComparison: React.FC { - // Mode states - const [beforeMode, setBeforeMode] = useState("rendered"); - const [afterMode, setAfterMode] = useState("rendered"); - - // Default available modes based on content type - const getDefaultModes = (type: ContentType): ContentMode[] => { - switch (type) { - case "html": - return ["rendered", "raw"]; - case "mermaid": - return ["rendered", "raw"]; - case "markdown": - return ["rendered", "raw"]; - case "image": - return ["rendered"]; - case "text": - return ["raw"]; - case "code": - return ["raw"]; - default: - return ["rendered"]; - } - }; + // Mode states - use defaultMode if provided, otherwise use first available mode + const [beforeMode, setBeforeMode] = useState(() => getInitialMode(before)); + const [afterMode, setAfterMode] = useState(() => getInitialMode(after)); const beforeModes = before.availableModes || getDefaultModes(before.type); const afterModes = after.availableModes || getDefaultModes(after.type); diff --git a/src/components/composites/Comparison/types.ts b/src/components/composites/Comparison/types.ts index 766af355..d7674a37 100644 --- a/src/components/composites/Comparison/types.ts +++ b/src/components/composites/Comparison/types.ts @@ -14,6 +14,7 @@ export interface ComparisonItem { label?: string; language?: string; // For code highlighting availableModes?: ContentMode[]; // Modes available for this item + defaultMode?: ContentMode; // Initial mode for this item customRender?: (content: unknown, mode: ContentMode) => React.ReactNode; } diff --git a/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx index ac00db77..ba9bb322 100644 --- a/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx +++ b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Comparison } from "../Comparison/Comparison"; -import type { ComparisonItem, ContentType, ComparisonLayout } from "../Comparison/types"; +import type { ComparisonItem, ContentType, ComparisonLayout, ContentMode } from "../Comparison/types"; import { TMetadata } from "../Markdowner/types"; interface ComparisonRendererProps { @@ -36,6 +36,8 @@ export const ComparisonRenderer: React.FC = ({ const layout = metadata.layout as ComparisonLayout | undefined; const height = (metadata.height as string) || "600px"; const syncModes = metadata.syncModes !== "false" && metadata.syncModes !== false; // default true + const leftDefaultMode = metadata.leftDefaultMode as ContentMode | undefined; + const rightDefaultMode = metadata.rightDefaultMode as ContentMode | undefined; // Validate type const validTypes: ContentType[] = ["html", "text", "code", "mermaid", "markdown", "image", "custom"]; @@ -62,6 +64,7 @@ export const ComparisonRenderer: React.FC = ({ type: type, label: leftLabel, language: language, + defaultMode: leftDefaultMode, }; const after: ComparisonItem = { @@ -69,6 +72,7 @@ export const ComparisonRenderer: React.FC = ({ type: type, label: rightLabel, language: language, + defaultMode: rightDefaultMode, }; return ( From 355def1c529623cec7d6d137ec1ef00ca8e21982 Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 11:25:12 -0300 Subject: [PATCH 08/20] fix(Comparison): adjust image rendering styles --- src/components/composites/Comparison/Comparison.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 79eb378e..099a1d10 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -132,6 +132,7 @@ const ContentRenderer: React.FC<{ src={String(item.content)} alt={item.label || "Comparison image"} className="w-full h-full object-cover" + style={{ maxWidth: "none", margin: 0 }} />
); From a783bccf4b98177e5156dbf9b77b6bf342cb663c Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 16:39:08 -0300 Subject: [PATCH 09/20] refactor(Comparison): use SwitchComponent for mode toggle - Replace mode toggle buttons with SwitchComponent and add i18n support. - Reorder after panel elements to show switch before label. --- .../composites/Comparison/Comparison.tsx | 132 +++++++----------- src/locales/en.json | 1 + src/locales/es.json | 1 + 3 files changed, 54 insertions(+), 80 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 099a1d10..712b84a7 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -7,6 +7,8 @@ import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; +import { useTranslation } from "react-i18next"; +import SwitchComponent from "../../ui/switch"; import type { ComparisonProps, ComparisonItem, ContentMode, ContentType, ComparisonLayout } from "./types"; // Internal component to render content based on type and mode @@ -127,11 +129,11 @@ const ContentRenderer: React.FC<{ case "image": return ( -
+
{item.label
@@ -198,6 +200,7 @@ const SliderComparison: React.FC> = ({ height = "600px", syncModes = true, }) => { + const { t } = useTranslation(); const [sliderPosition, setSliderPosition] = useState(defaultPosition); const [isDragging, setIsDragging] = useState(false); @@ -249,26 +252,20 @@ const SliderComparison: React.FC> = ({ updateSliderPosition(touch.clientX); }; - // Toggle mode handlers - const toggleBeforeMode = () => { - const currentIndex = beforeModes.indexOf(beforeMode); - const nextIndex = (currentIndex + 1) % beforeModes.length; - const nextMode = beforeModes[nextIndex]; - - setBeforeMode(nextMode); + // Mode change handlers for SwitchComponent + const handleBeforeModeChange = (checked: boolean) => { + const newMode: ContentMode = checked ? "rendered" : "raw"; + setBeforeMode(newMode); if (syncModes) { - setAfterMode(nextMode); + setAfterMode(newMode); } }; - const toggleAfterMode = () => { - const currentIndex = afterModes.indexOf(afterMode); - const nextIndex = (currentIndex + 1) % afterModes.length; - const nextMode = afterModes[nextIndex]; - - setAfterMode(nextMode); + const handleAfterModeChange = (checked: boolean) => { + const newMode: ContentMode = checked ? "rendered" : "raw"; + setAfterMode(newMode); if (syncModes) { - setBeforeMode(nextMode); + setBeforeMode(newMode); } }; @@ -296,14 +293,6 @@ const SliderComparison: React.FC> = ({ }; }, [isDragging, handleMouseMove, handleMouseUp]); - const getModeLabel = (mode: ContentMode) => { - return mode === "rendered" ? "👁️ Vista" : "📝 Código"; - }; - - const getModeTitle = (mode: ContentMode) => { - return mode === "rendered" ? "Ver Código" : "Ver Renderizado"; - }; - return (
> = ({ style={{ pointerEvents: isDragging ? "none" : "auto" }} >
+ {afterModes.length > 1 && ( + + )} {after.label && (
{after.label}
)} - {afterModes.length > 1 && ( - - )}
@@ -352,13 +340,12 @@ const SliderComparison: React.FC> = ({
)} {beforeModes.length > 1 && ( - + )}
@@ -399,6 +386,7 @@ const SideBySideComparison: React.FC { + const { t } = useTranslation(); // Mode states - use defaultMode if provided, otherwise use first available mode const [beforeMode, setBeforeMode] = useState(() => getInitialMode(before)); const [afterMode, setAfterMode] = useState(() => getInitialMode(after)); @@ -406,37 +394,23 @@ const SideBySideComparison: React.FC { - const currentIndex = beforeModes.indexOf(beforeMode); - const nextIndex = (currentIndex + 1) % beforeModes.length; - const nextMode = beforeModes[nextIndex]; - - setBeforeMode(nextMode); + // Mode change handlers for SwitchComponent + const handleBeforeModeChange = (checked: boolean) => { + const newMode: ContentMode = checked ? "rendered" : "raw"; + setBeforeMode(newMode); if (syncModes) { - setAfterMode(nextMode); + setAfterMode(newMode); } }; - const toggleAfterMode = () => { - const currentIndex = afterModes.indexOf(afterMode); - const nextIndex = (currentIndex + 1) % afterModes.length; - const nextMode = afterModes[nextIndex]; - - setAfterMode(nextMode); + const handleAfterModeChange = (checked: boolean) => { + const newMode: ContentMode = checked ? "rendered" : "raw"; + setAfterMode(newMode); if (syncModes) { - setBeforeMode(nextMode); + setBeforeMode(newMode); } }; - const getModeLabel = (mode: ContentMode) => { - return mode === "rendered" ? "👁️ Vista" : "📝 Código"; - }; - - const getModeTitle = (mode: ContentMode) => { - return mode === "rendered" ? "Ver Código" : "Ver Renderizado"; - }; - return (
)} {beforeModes.length > 1 && ( - + )}
@@ -467,20 +440,19 @@ const SideBySideComparison: React.FC
+ {afterModes.length > 1 && ( + + )} {after.label && (
{after.label}
)} - {afterModes.length > 1 && ( - - )}
diff --git a/src/locales/en.json b/src/locales/en.json index c4755fcd..eedb7854 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -129,6 +129,7 @@ "we-got-you-covered": "We got you covered", "please-select-option": "Please select an option", "code": "Code", + "preview": "Preview", "output": "Output", "compile-first": "You must compile or test your code to see the output", "or": "or", diff --git a/src/locales/es.json b/src/locales/es.json index 18cbc600..91045f93 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -136,6 +136,7 @@ "we-got-you-covered": "Te tenemos cubierto", "please-select-option": "Por favor selecciona una opción", "code": "Código", + "preview": "Vista previa", "output": "Salida", "compile-first": "Tienes que compilar o testear tu código para ver la salida", "or": "o", From c834ec9d339122744bd55a65fbfb95a57c7d634d Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 16:55:30 -0300 Subject: [PATCH 10/20] refactor(Comparison): improve header styles in SideBySide layout --- src/components/composites/Comparison/Comparison.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 712b84a7..0f6642b3 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -419,7 +419,7 @@ const SideBySideComparison: React.FC {/* Before Panel */}
-
+
{before.label && (
{before.label} @@ -439,7 +439,7 @@ const SideBySideComparison: React.FC -
+
{afterModes.length > 1 && ( Date: Tue, 30 Dec 2025 17:15:50 -0300 Subject: [PATCH 11/20] fix(comparison): hide right panel controls at right edge Apply clipPath to After panel to hide label and switch when slider position reaches 100%, ensuring symmetric behavior. --- src/components/composites/Comparison/Comparison.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 0f6642b3..16f03cb3 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -304,7 +304,10 @@ const SliderComparison: React.FC> = ({ {/* Background Layer (After) */}
{afterModes.length > 1 && ( From 3da7f47d08a269ecbb8ed9943cabe5011632f6dc Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 17:34:19 -0300 Subject: [PATCH 12/20] feat(comparison): add sticky white background to controls in raw mode Apply sticky positioning with white background to SliderComparison controls when in raw mode, consistent with SideBySideComparison layout. --- src/components/composites/Comparison/Comparison.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 16f03cb3..28d0548a 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -309,7 +309,7 @@ const SliderComparison: React.FC> = ({ pointerEvents: isDragging ? "none" : "auto" }} > -
+
{afterModes.length > 1 && ( > = ({ pointerEvents: isDragging ? "none" : "auto", }} > -
+
{before.label && (
{before.label} From 48656a2188c432c7997ddab52f6ac5f540e608cd Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 17:45:38 -0300 Subject: [PATCH 13/20] fix(Comparison): set slider-divider height to 100% --- src/components/composites/Comparison/Comparison.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 28d0548a..ba6e6920 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -357,8 +357,8 @@ const SliderComparison: React.FC> = ({ {/* Slider Divider */}
From 150fddf5003cd7e35feaca169cf9b05b1d2cf913 Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 18:00:08 -0300 Subject: [PATCH 14/20] feat(Comparison): adjust controls order and alignment on mobile --- src/components/composites/Comparison/Comparison.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index ba6e6920..0bb357f6 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -442,7 +442,7 @@ const SideBySideComparison: React.FC -
+
{afterModes.length > 1 && ( Date: Tue, 30 Dec 2025 18:52:18 -0300 Subject: [PATCH 15/20] refactor(comparison): use left/right instead of before/after Rename props and variables to better reflect component's broader use cases beyond temporal comparisons. Update all related files. --- docs/explanatory_components.yml | 14 +- .../composites/Comparison/Comparison.tsx | 126 +++++++++--------- src/components/composites/Comparison/types.ts | 4 +- .../ComparisonRenderer/ComparisonRenderer.tsx | 8 +- 4 files changed, 76 insertions(+), 76 deletions(-) diff --git a/docs/explanatory_components.yml b/docs/explanatory_components.yml index 28291195..544169da 100644 --- a/docs/explanatory_components.yml +++ b/docs/explanatory_components.yml @@ -50,7 +50,7 @@ components: - explanation - educational comparison - code improvement demonstration - - before/after visualization + - side-by-side or slider visualization description: >- The comparison component allows side-by-side or slider-based comparison of two pieces of content. It supports multiple content types including HTML previews, code with syntax highlighting, text, @@ -58,16 +58,16 @@ components: or comparing different approaches to solve the same problem. The component automatically selects the best layout based on content type: slider for visual content (HTML, images) and side-by-side for textual/code content. goodFor: - - showing code before and after refactoring or bug fixes + - showing code improvements through refactoring or bug fixes - comparing correct vs incorrect implementations - demonstrating design improvements (images, HTML) - comparing simple vs detailed prompts or explanations - showing Mermaid diagram code vs rendered output (using leftDefaultMode="raw" and rightDefaultMode="rendered") - showing Markdown source vs rendered output (using different defaultMode for each panel) - comparing different algorithms or approaches side by side - - visual before/after demonstrations of UI changes + - visual demonstrations of UI changes or variations - comparing code with different levels of complexity or optimization - whenToUse: Use when showing two versions of something enhances learning - comparing approaches, demonstrating improvements, or showing the evolution from a problem to its solution. Particularly effective for code quality improvements, bug fixes, design iterations, or when teaching best practices by contrasting good and bad examples. + whenToUse: Use when showing two versions or variations of something enhances learning - comparing approaches, demonstrating improvements, or showing different solutions to the same problem. Particularly effective for code quality improvements, bug fixes, design iterations, or when teaching best practices by contrasting different examples. avoid: - comparing more than 2 items (component only supports 2 panels) - using for unrelated content that doesn't benefit from comparison @@ -96,7 +96,7 @@ components: - leftDefaultMode - Initial display mode for left panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type - rightDefaultMode - Initial display mode for right panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type example: | - ```comparison type="html" left="Sin estilos" right="Con estilos CSS" + ```comparison type="html" left="Versión simple" right="Versión mejorada"

Título básico

Párrafo sin formato

@@ -111,7 +111,7 @@ components: ``` --- Second Example --- - ```comparison type="code" language="python" left="Código ineficiente" right="Código optimizado" + ```comparison type="code" language="python" left="Versión básica" right="Versión optimizada" def find_max(numbers): sorted_numbers = sorted(numbers) return sorted_numbers[-1] @@ -123,7 +123,7 @@ components: ``` --- Third Example --- - ```comparison type="text" left="Prompt vago" right="Prompt específico" height="400px" + ```comparison type="text" left="Versión simple" right="Versión detallada" height="400px" Explica las funciones en Python ---SEPARATOR--- diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 0bb357f6..3eab32db 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -149,10 +149,10 @@ const ContentRenderer: React.FC<{ }; // Determine default layout based on content types -const determineLayout = (before: ComparisonItem, after: ComparisonItem): ComparisonLayout => { +const determineLayout = (left: ComparisonItem, right: ComparisonItem): ComparisonLayout => { // Use slider for visual content (HTML and images) - if (before.type === "html" || after.type === "html" || - before.type === "image" || after.type === "image") { + if (left.type === "html" || right.type === "html" || + left.type === "image" || right.type === "image") { return "slider"; } @@ -194,8 +194,8 @@ const getInitialMode = (item: ComparisonItem): ContentMode => { // Slider Comparison Component (original behavior) const SliderComparison: React.FC> = ({ - before, - after, + left, + right, defaultPosition = 50, height = "600px", syncModes = true, @@ -205,13 +205,13 @@ const SliderComparison: React.FC> = ({ const [isDragging, setIsDragging] = useState(false); // Mode states - use defaultMode if provided, otherwise use first available mode - const [beforeMode, setBeforeMode] = useState(() => getInitialMode(before)); - const [afterMode, setAfterMode] = useState(() => getInitialMode(after)); + const [leftMode, setLeftMode] = useState(() => getInitialMode(left)); + const [rightMode, setRightMode] = useState(() => getInitialMode(right)); const containerRef = useRef(null); - const beforeModes = before.availableModes || getDefaultModes(before.type); - const afterModes = after.availableModes || getDefaultModes(after.type); + const leftModes = left.availableModes || getDefaultModes(left.type); + const rightModes = right.availableModes || getDefaultModes(right.type); const updateSliderPosition = useCallback((clientX: number) => { if (!containerRef.current) return; @@ -253,19 +253,19 @@ const SliderComparison: React.FC> = ({ }; // Mode change handlers for SwitchComponent - const handleBeforeModeChange = (checked: boolean) => { + const handleLeftModeChange = (checked: boolean) => { const newMode: ContentMode = checked ? "rendered" : "raw"; - setBeforeMode(newMode); + setLeftMode(newMode); if (syncModes) { - setAfterMode(newMode); + setRightMode(newMode); } }; - const handleAfterModeChange = (checked: boolean) => { + const handleRightModeChange = (checked: boolean) => { const newMode: ContentMode = checked ? "rendered" : "raw"; - setAfterMode(newMode); + setRightMode(newMode); if (syncModes) { - setBeforeMode(newMode); + setLeftMode(newMode); } }; @@ -301,7 +301,7 @@ const SliderComparison: React.FC> = ({ onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} > - {/* Background Layer (After) */} + {/* Background Layer (Right) */}
> = ({ pointerEvents: isDragging ? "none" : "auto" }} > -
- {afterModes.length > 1 && ( +
+ {rightModes.length > 1 && ( )} - {after.label && ( + {right.label && (
- {after.label} + {right.label}
)}
- +
- {/* Foreground Layer (Before) - Clipped */} + {/* Foreground Layer (Left) - Clipped */}
> = ({ pointerEvents: isDragging ? "none" : "auto", }} > -
- {before.label && ( +
+ {left.label && (
- {before.label} + {left.label}
)} - {beforeModes.length > 1 && ( + {leftModes.length > 1 && ( )}
- +
{/* Slider Divider */} @@ -384,33 +384,33 @@ const SliderComparison: React.FC> = ({ // Side-by-Side Comparison Component (new) const SideBySideComparison: React.FC> = ({ - before, - after, + left, + right, height = "600px", syncModes = true, }) => { const { t } = useTranslation(); // Mode states - use defaultMode if provided, otherwise use first available mode - const [beforeMode, setBeforeMode] = useState(() => getInitialMode(before)); - const [afterMode, setAfterMode] = useState(() => getInitialMode(after)); + const [leftMode, setLeftMode] = useState(() => getInitialMode(left)); + const [rightMode, setRightMode] = useState(() => getInitialMode(right)); - const beforeModes = before.availableModes || getDefaultModes(before.type); - const afterModes = after.availableModes || getDefaultModes(after.type); + const leftModes = left.availableModes || getDefaultModes(left.type); + const rightModes = right.availableModes || getDefaultModes(right.type); // Mode change handlers for SwitchComponent - const handleBeforeModeChange = (checked: boolean) => { + const handleLeftModeChange = (checked: boolean) => { const newMode: ContentMode = checked ? "rendered" : "raw"; - setBeforeMode(newMode); + setLeftMode(newMode); if (syncModes) { - setAfterMode(newMode); + setRightMode(newMode); } }; - const handleAfterModeChange = (checked: boolean) => { + const handleRightModeChange = (checked: boolean) => { const newMode: ContentMode = checked ? "rendered" : "raw"; - setAfterMode(newMode); + setRightMode(newMode); if (syncModes) { - setBeforeMode(newMode); + setLeftMode(newMode); } }; @@ -420,44 +420,44 @@ const SideBySideComparison: React.FC
- {/* Before Panel */} + {/* Left Panel */}
- {before.label && ( + {left.label && (
- {before.label} + {left.label}
)} - {beforeModes.length > 1 && ( + {leftModes.length > 1 && ( )}
- +
- {/* After Panel */} + {/* Right Panel */}
- {afterModes.length > 1 && ( + {rightModes.length > 1 && ( )} - {after.label && ( + {right.label && (
- {after.label} + {right.label}
)}
- +
@@ -466,10 +466,10 @@ const SideBySideComparison: React.FC = (props) => { - const { before, after, layout } = props; + const { left, right, layout } = props; // Determine layout automatically if not specified - const effectiveLayout = layout || determineLayout(before, after); + const effectiveLayout = layout || determineLayout(left, right); // Render the appropriate layout if (effectiveLayout === "slider") { diff --git a/src/components/composites/Comparison/types.ts b/src/components/composites/Comparison/types.ts index d7674a37..4f7871ca 100644 --- a/src/components/composites/Comparison/types.ts +++ b/src/components/composites/Comparison/types.ts @@ -19,8 +19,8 @@ export interface ComparisonItem { } export interface ComparisonProps { - before: ComparisonItem; - after: ComparisonItem; + left: ComparisonItem; + right: ComparisonItem; defaultPosition?: number; // 0-100, default 50 (only for slider layout) height?: string; // CSS height value, default "600px" syncModes?: boolean; // If true, both panels change mode together (default: true) diff --git a/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx index ba9bb322..37b9292c 100644 --- a/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx +++ b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx @@ -59,7 +59,7 @@ export const ComparisonRenderer: React.FC = ({ } // Build comparison items - const before: ComparisonItem = { + const left: ComparisonItem = { content: leftContent, type: type, label: leftLabel, @@ -67,7 +67,7 @@ export const ComparisonRenderer: React.FC = ({ defaultMode: leftDefaultMode, }; - const after: ComparisonItem = { + const right: ComparisonItem = { content: rightContent, type: type, label: rightLabel, @@ -78,8 +78,8 @@ export const ComparisonRenderer: React.FC = ({ return (
Date: Tue, 30 Dec 2025 19:29:46 -0300 Subject: [PATCH 16/20] refactor(comparison): improve metadata property names for better AI clarity --- docs/explanatory_components.yml | 30 +++++++++---------- .../ComparisonRenderer/ComparisonRenderer.tsx | 16 +++++----- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/explanatory_components.yml b/docs/explanatory_components.yml index 544169da..5f271452 100644 --- a/docs/explanatory_components.yml +++ b/docs/explanatory_components.yml @@ -62,8 +62,8 @@ components: - comparing correct vs incorrect implementations - demonstrating design improvements (images, HTML) - comparing simple vs detailed prompts or explanations - - showing Mermaid diagram code vs rendered output (using leftDefaultMode="raw" and rightDefaultMode="rendered") - - showing Markdown source vs rendered output (using different defaultMode for each panel) + - showing Mermaid diagram code vs rendered output (using leftInitialMode="raw" and rightInitialMode="rendered") + - showing Markdown source vs rendered output (using different initialMode for each panel) - comparing different algorithms or approaches side by side - visual demonstrations of UI changes or variations - comparing code with different levels of complexity or optimization @@ -79,24 +79,24 @@ components: - The type attribute is REQUIRED and must be one of these values - html, text, code, mermaid, markdown, image - For type="code", you MUST also specify the language attribute (e.g. language="python", language="javascript") - Both content blocks should be comparable in length and complexity for best visual results - - Use descriptive left and right labels to guide the student and provide context + - Use descriptive leftLabel and rightLabel to guide the student and provide context - For images, use full URLs or paths starting with ./.learn/assets/ - Keep the compared content focused on a single concept or improvement - The separator must be on its own line with no extra spaces or characters - - To show code vs rendered output, use leftDefaultMode="raw" and rightDefaultMode="rendered" with syncModes="false" - - When using different defaultModes, consider setting syncModes="false" so each panel can be toggled independently + - To show code vs rendered output, use leftInitialMode="raw" and rightInitialMode="rendered" with syncRenderToggle="false" + - When using different initialModes, consider setting syncRenderToggle="false" so each panel can be toggled independently metadata: - type - Content type (REQUIRED) - Must be one of html, text, code, mermaid, markdown, image - language - Programming language (REQUIRED only for type="code") - python, javascript, java, etc - - left - Label for left panel (optional but recommended) - Describes what the left content represents - - right - Label for right panel (optional but recommended) - Describes what the right content represents + - leftLabel - Label for left panel (optional but recommended) - Describes what the left content represents + - rightLabel - Label for right panel (optional but recommended) - Describes what the right content represents - layout - Force specific layout (optional) - "slider" for overlay comparison or "side-by-side" for split view. Auto-detected if omitted - height - Component height (optional) - Default is "600px", accepts CSS values like "500px", "80vh", "700px" - - syncModes - Sync render/code toggle between panels (optional) - "true" or "false", default is "true" - - leftDefaultMode - Initial display mode for left panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type - - rightDefaultMode - Initial display mode for right panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type + - syncRenderToggle - Sync render/code toggle between panels (optional) - "true" or "false", default is "true". When true, changing mode in one panel also changes the other + - leftInitialMode - Initial display mode for left panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type + - rightInitialMode - Initial display mode for right panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type example: | - ```comparison type="html" left="Versión simple" right="Versión mejorada" + ```comparison type="html" leftLabel="Versión simple" rightLabel="Versión mejorada"

Título básico

Párrafo sin formato

@@ -111,7 +111,7 @@ components: ``` --- Second Example --- - ```comparison type="code" language="python" left="Versión básica" right="Versión optimizada" + ```comparison type="code" language="python" leftLabel="Versión básica" rightLabel="Versión optimizada" def find_max(numbers): sorted_numbers = sorted(numbers) return sorted_numbers[-1] @@ -123,7 +123,7 @@ components: ``` --- Third Example --- - ```comparison type="text" left="Versión simple" right="Versión detallada" height="400px" + ```comparison type="text" leftLabel="Versión simple" rightLabel="Versión detallada" height="400px" Explica las funciones en Python ---SEPARATOR--- @@ -136,7 +136,7 @@ components: ``` --- Fourth Example --- - ```comparison type="code" language="javascript" left="Versión con bucle" right="Versión con método" + ```comparison type="code" language="javascript" leftLabel="Versión con bucle" rightLabel="Versión con método" function sumArray(arr) { let sum = 0; for(let i = 0; i < arr.length; i++) { @@ -153,7 +153,7 @@ components: ``` --- Fifth Example (with different initial modes) --- - ```comparison type="mermaid" left="Mermaid Code" right="Rendered Diagram" syncModes="false" leftDefaultMode="raw" rightDefaultMode="rendered" + ```comparison type="mermaid" leftLabel="Mermaid Code" rightLabel="Rendered Diagram" syncRenderToggle="false" leftInitialMode="raw" rightInitialMode="rendered" graph TD A[Start] --> B{Decision} B -->|Yes| C[Action 1] diff --git a/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx index 37b9292c..75bf19a9 100644 --- a/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx +++ b/src/components/composites/ComparisonRenderer/ComparisonRenderer.tsx @@ -31,13 +31,13 @@ export const ComparisonRenderer: React.FC = ({ // Extract and validate metadata const type = (metadata.type as string || "code") as ContentType; const language = metadata.language as string | undefined; - const leftLabel = metadata.left as string | undefined; - const rightLabel = metadata.right as string | undefined; + const leftLabel = metadata.leftLabel as string | undefined; + const rightLabel = metadata.rightLabel as string | undefined; const layout = metadata.layout as ComparisonLayout | undefined; const height = (metadata.height as string) || "600px"; - const syncModes = metadata.syncModes !== "false" && metadata.syncModes !== false; // default true - const leftDefaultMode = metadata.leftDefaultMode as ContentMode | undefined; - const rightDefaultMode = metadata.rightDefaultMode as ContentMode | undefined; + const syncRenderToggle = metadata.syncRenderToggle !== "false" && metadata.syncRenderToggle !== false; // default true + const leftInitialMode = metadata.leftInitialMode as ContentMode | undefined; + const rightInitialMode = metadata.rightInitialMode as ContentMode | undefined; // Validate type const validTypes: ContentType[] = ["html", "text", "code", "mermaid", "markdown", "image", "custom"]; @@ -64,7 +64,7 @@ export const ComparisonRenderer: React.FC = ({ type: type, label: leftLabel, language: language, - defaultMode: leftDefaultMode, + defaultMode: leftInitialMode, }; const right: ComparisonItem = { @@ -72,7 +72,7 @@ export const ComparisonRenderer: React.FC = ({ type: type, label: rightLabel, language: language, - defaultMode: rightDefaultMode, + defaultMode: rightInitialMode, }; return ( @@ -82,7 +82,7 @@ export const ComparisonRenderer: React.FC = ({ right={right} layout={layout} height={height} - syncModes={syncModes} + syncModes={syncRenderToggle} />
); From 1601a73c8fd9609fa93f10864ae520b60dab0cde Mon Sep 17 00:00:00 2001 From: mfagundez Date: Tue, 30 Dec 2025 20:10:10 -0300 Subject: [PATCH 17/20] refactor(docs): split comparison into specific use-case components - Replace generic comparison component with code_comparison, html_comparison, and code_vs_rendered - Each component now lists only relevant metadata props for its use case --- docs/explanatory_components.yml | 272 ++++++++++++++++++++------------ 1 file changed, 173 insertions(+), 99 deletions(-) diff --git a/docs/explanatory_components.yml b/docs/explanatory_components.yml index 5f271452..d5425998 100644 --- a/docs/explanatory_components.yml +++ b/docs/explanatory_components.yml @@ -45,130 +45,204 @@ components: [Ask Rigo how to create a function in Python](https://4geeks.com/ask?query=help-me-understand-how-to-create-a-function-in-python-in-the-simplest-possible-way) - - name: comparison + - name: code_comparison intendedUse: - - explanation - - educational comparison - - code improvement demonstration - - side-by-side or slider visualization + - comparing code versions + - showing bug fixes or improvements + - demonstrating different algorithmic approaches description: >- - The comparison component allows side-by-side or slider-based comparison of two pieces of content. - It supports multiple content types including HTML previews, code with syntax highlighting, text, - Mermaid diagrams, Markdown rendering, and images. Perfect for showing code improvements, design changes, - or comparing different approaches to solve the same problem. The component automatically selects the best - layout based on content type: slider for visual content (HTML, images) and side-by-side for textual/code content. + Compare two code blocks side-by-side to show improvements, bug fixes, refactoring, or different approaches to solve the same problem. This is the most common comparison use case. goodFor: - - showing code improvements through refactoring or bug fixes - - comparing correct vs incorrect implementations - - demonstrating design improvements (images, HTML) - - comparing simple vs detailed prompts or explanations - - showing Mermaid diagram code vs rendered output (using leftInitialMode="raw" and rightInitialMode="rendered") - - showing Markdown source vs rendered output (using different initialMode for each panel) - - comparing different algorithms or approaches side by side - - visual demonstrations of UI changes or variations - - comparing code with different levels of complexity or optimization - whenToUse: Use when showing two versions or variations of something enhances learning - comparing approaches, demonstrating improvements, or showing different solutions to the same problem. Particularly effective for code quality improvements, bug fixes, design iterations, or when teaching best practices by contrasting different examples. + - showing before/after code improvements + - comparing simple vs optimized implementations + - demonstrating bug fixes with validation + - contrasting iterative vs functional approaches + - showing different coding patterns or best practices + whenToUse: Use when comparing two versions of code enhances learning. Perfect for teaching refactoring, debugging, optimization, or comparing different solutions to the same problem. avoid: - - comparing more than 2 items (component only supports 2 panels) - - using for unrelated content that doesn't benefit from comparison - - overusing in a single lesson (can be overwhelming, use sparingly) - - very long code blocks that make scrolling difficult (keep comparisons focused) - - using when a simple explanation would be clearer + - comparing unrelated code snippets + - very long code blocks (keep focused on the key differences) + - more than one concept per comparison rules: - - CRITICAL - Must use exactly the text "---SEPARATOR---" on its own line to divide the two content blocks - - The type attribute is REQUIRED and must be one of these values - html, text, code, mermaid, markdown, image - - For type="code", you MUST also specify the language attribute (e.g. language="python", language="javascript") - - Both content blocks should be comparable in length and complexity for best visual results - - Use descriptive leftLabel and rightLabel to guide the student and provide context - - For images, use full URLs or paths starting with ./.learn/assets/ - - Keep the compared content focused on a single concept or improvement - - The separator must be on its own line with no extra spaces or characters - - To show code vs rendered output, use leftInitialMode="raw" and rightInitialMode="rendered" with syncRenderToggle="false" - - When using different initialModes, consider setting syncRenderToggle="false" so each panel can be toggled independently + - CRITICAL - Must use exactly "---SEPARATOR---" on its own line to divide the two code blocks + - type="code" is REQUIRED + - language attribute is REQUIRED (e.g. language="python", language="javascript") + - Use descriptive leftLabel and rightLabel to guide the student + - Keep both code blocks similar in length for best visual results metadata: - - type - Content type (REQUIRED) - Must be one of html, text, code, mermaid, markdown, image - - language - Programming language (REQUIRED only for type="code") - python, javascript, java, etc - - leftLabel - Label for left panel (optional but recommended) - Describes what the left content represents - - rightLabel - Label for right panel (optional but recommended) - Describes what the right content represents - - layout - Force specific layout (optional) - "slider" for overlay comparison or "side-by-side" for split view. Auto-detected if omitted - - height - Component height (optional) - Default is "600px", accepts CSS values like "500px", "80vh", "700px" - - syncRenderToggle - Sync render/code toggle between panels (optional) - "true" or "false", default is "true". When true, changing mode in one panel also changes the other - - leftInitialMode - Initial display mode for left panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type - - rightInitialMode - Initial display mode for right panel (optional) - "rendered" or "raw". Defaults to first available mode for the content type + - type - Must be "code" + - language - REQUIRED - Programming language (python, javascript, java, etc) + - leftLabel - Recommended - Label for left panel (e.g. "Basic Version", "With Bug") + - rightLabel - Recommended - Label for right panel (e.g. "Optimized", "Fixed Version") example: | - ```comparison type="html" leftLabel="Versión simple" rightLabel="Versión mejorada" -

Título básico

-

Párrafo sin formato

+ ```comparison type="code" language="python" leftLabel="Simple Version" rightLabel="With Validation" + def factorial(n): + result = 1 + for i in range(1, n + 1): + result *= i + return result ---SEPARATOR--- - -

Título estilizado

-

Párrafo con formato mejorado

+ def factorial(n): + """Calculate factorial with validation.""" + if not isinstance(n, int) or n < 0: + raise ValueError("Must be a positive integer") + if n == 0 or n == 1: + return 1 + return n * factorial(n - 1) ``` --- Second Example --- - ```comparison type="code" language="python" leftLabel="Versión básica" rightLabel="Versión optimizada" - def find_max(numbers): - sorted_numbers = sorted(numbers) - return sorted_numbers[-1] + ```comparison type="code" language="javascript" leftLabel="With Bug" rightLabel="Fixed" + function greet(name) { + console.log("Hello " + name) + } + greet() // Hello undefined ---SEPARATOR--- - def find_max(numbers): - return max(numbers) + function greet(name) { + if (!name) { + console.log("Hello, stranger!"); + return; + } + console.log("Hello " + name); + } + greet() // Hello, stranger! ``` - --- Third Example --- - ```comparison type="text" leftLabel="Versión simple" rightLabel="Versión detallada" height="400px" - Explica las funciones en Python + - name: html_comparison + intendedUse: + - comparing visual designs + - showing CSS improvements + - demonstrating UI changes with slider + description: >- + Compare two HTML/CSS implementations using an interactive slider. Best for visual comparisons where design changes are the focus. The slider allows students to see differences by dragging. + goodFor: + - showing before/after design improvements + - comparing different CSS layout techniques (Flexbox vs Grid) + - demonstrating responsive design changes + - visual UI enhancements and styling improvements + whenToUse: Use when visual comparison is more important than code comparison. The slider layout is automatic for HTML content and provides an intuitive way to see design differences. + avoid: + - when code comparison is more important than visual result + - comparing completely different page structures (focus on similar layouts) + rules: + - CRITICAL - Must use exactly "---SEPARATOR---" on its own line to divide the two HTML blocks + - type="html" is REQUIRED + - Use descriptive leftLabel and rightLabel to describe each version + - Both HTML blocks should represent comparable designs for best effect + - Can force layout="side-by-side" if you prefer code comparison over visual slider + metadata: + - type - Must be "html" + - leftLabel - Recommended - Label for left panel (e.g. "Basic Design", "Flexbox") + - rightLabel - Recommended - Label for right panel (e.g. "Enhanced", "CSS Grid") + - layout - Optional - Use "side-by-side" to force code comparison instead of slider + example: | + ```comparison type="html" leftLabel="Basic Styles" rightLabel="Enhanced Design" + + + + + + +

Original Version

+

Basic styling applied.

+ + ---SEPARATOR--- - Explica las funciones en Python de forma clara: - - Sintaxis para definir funciones - - Cómo usar parámetros y argumentos - - Valores de retorno con return - - Ejemplos prácticos aplicados + + + + + + +

Enhanced Version

+

Modern gradient and improved styling.

+ + + ``` - --- Fourth Example --- - - ```comparison type="code" language="javascript" leftLabel="Versión con bucle" rightLabel="Versión con método" - function sumArray(arr) { - let sum = 0; - for(let i = 0; i < arr.length; i++) { - sum = sum + arr[i]; - } - return sum; - } - ---SEPARATOR--- + - name: code_vs_rendered + intendedUse: + - teaching markup syntax (Markdown, Mermaid) + - showing source code and its rendered output + description: >- + Show source code on one side and its rendered output on the other. Perfect for teaching Markdown or Mermaid syntax by displaying the raw code and final result simultaneously. + goodFor: + - teaching Markdown syntax + - teaching Mermaid diagram syntax + - showing how markup translates to visual output + whenToUse: Use when teaching syntax where seeing both the source and rendered result together enhances understanding. Set different initial modes for each panel and disable sync. + avoid: + - when simple code-only comparison is sufficient + - for HTML (where preview is already obvious) + rules: + - CRITICAL - Must use exactly "---SEPARATOR---" on its own line (can use same content on both sides) + - type must be "mermaid" or "markdown" + - MUST set leftInitialMode="raw" and rightInitialMode="rendered" + - MUST set syncRenderToggle="false" to allow independent panel control + - Both sides typically contain the SAME content (shown differently) + metadata: + - type - Must be "mermaid" or "markdown" + - leftLabel - Recommended - Usually "Source Code" or "Markdown Code" + - rightLabel - Recommended - Usually "Rendered Output" or "Result" + - leftInitialMode - REQUIRED - Must be "raw" + - rightInitialMode - REQUIRED - Must be "rendered" + - syncRenderToggle - REQUIRED - Must be "false" + example: | + ```comparison type="mermaid" leftLabel="Mermaid Code" rightLabel="Rendered Diagram" leftInitialMode="raw" rightInitialMode="rendered" syncRenderToggle="false" + sequenceDiagram + participant User + participant Frontend + participant Backend + + User->>Frontend: Request Data + Frontend->>Backend: API Call + Backend-->>Frontend: Response + Frontend-->>User: Display Data - function sumArray(arr) { - return arr.reduce((sum, num) => sum + num, 0); - } - ``` - --- Fifth Example (with different initial modes) --- - - ```comparison type="mermaid" leftLabel="Mermaid Code" rightLabel="Rendered Diagram" syncRenderToggle="false" leftInitialMode="raw" rightInitialMode="rendered" - graph TD - A[Start] --> B{Decision} - B -->|Yes| C[Action 1] - B -->|No| D[Action 2] - C --> E[End] - D --> E - ---SEPARATOR--- - - graph TD - A[Start] --> B{Decision} - B -->|Yes| C[Action 1] - B -->|No| D[Action 2] - C --> E[End] - D --> E + + sequenceDiagram + participant User + participant Frontend + participant Backend + + User->>Frontend: Request Data + Frontend->>Backend: API Call + Backend-->>Frontend: Response + Frontend-->>User: Display Data ``` - name: image From e65bd7fb41da849322ec0ee9f52f64432433d289 Mon Sep 17 00:00:00 2001 From: mfagundez Date: Wed, 31 Dec 2025 09:56:17 -0300 Subject: [PATCH 18/20] refactor(Comparison): simplify control positioning in SliderComparison component --- src/components/composites/Comparison/Comparison.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/composites/Comparison/Comparison.tsx b/src/components/composites/Comparison/Comparison.tsx index 3eab32db..3e2385b6 100644 --- a/src/components/composites/Comparison/Comparison.tsx +++ b/src/components/composites/Comparison/Comparison.tsx @@ -309,7 +309,7 @@ const SliderComparison: React.FC> = ({ pointerEvents: isDragging ? "none" : "auto" }} > -
+
{rightModes.length > 1 && ( > = ({ pointerEvents: isDragging ? "none" : "auto", }} > -
+
{left.label && (
{left.label} From 7c4446fce12e016788bb9e75e5cde4e67f42b55e Mon Sep 17 00:00:00 2001 From: mfagundez Date: Wed, 31 Dec 2025 11:37:03 -0300 Subject: [PATCH 19/20] refactor(docs): enhance HTML/CSS comparison guidelines --- docs/explanatory_components.yml | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/explanatory_components.yml b/docs/explanatory_components.yml index d5425998..873eda26 100644 --- a/docs/explanatory_components.yml +++ b/docs/explanatory_components.yml @@ -58,20 +58,23 @@ components: - demonstrating bug fixes with validation - contrasting iterative vs functional approaches - showing different coding patterns or best practices - whenToUse: Use when comparing two versions of code enhances learning. Perfect for teaching refactoring, debugging, optimization, or comparing different solutions to the same problem. + whenToUse: Use when comparing two versions of code enhances learning. Perfect for teaching refactoring, debugging, optimization, or comparing different solutions to the same problem. For HTML/CSS comparisons, prefer html_comparison which offers both visual and code comparison modes. avoid: - comparing unrelated code snippets - very long code blocks (keep focused on the key differences) - more than one concept per comparison + - HTML code (use html_comparison instead for better flexibility with raw/rendered modes) rules: + - CRITICAL - NEVER use this for HTML code, always use html_comparison component instead - CRITICAL - Must use exactly "---SEPARATOR---" on its own line to divide the two code blocks - type="code" is REQUIRED - language attribute is REQUIRED (e.g. language="python", language="javascript") + - NEVER set language="html" - use html_comparison component instead - Use descriptive leftLabel and rightLabel to guide the student - Keep both code blocks similar in length for best visual results metadata: - type - Must be "code" - - language - REQUIRED - Programming language (python, javascript, java, etc) + - language - REQUIRED - Programming language (python, javascript, java, etc.) NEVER "html" - leftLabel - Recommended - Label for left panel (e.g. "Basic Version", "With Bug") - rightLabel - Recommended - Label for right panel (e.g. "Optimized", "Fixed Version") example: | @@ -117,28 +120,34 @@ components: - comparing visual designs - showing CSS improvements - demonstrating UI changes with slider + - comparing HTML structure and semantics description: >- - Compare two HTML/CSS implementations using an interactive slider. Best for visual comparisons where design changes are the focus. The slider allows students to see differences by dragging. + Compare two HTML/CSS implementations. This component handles both visual comparisons (using an interactive slider) and code structure comparisons (using side-by-side layout with raw mode). ALWAYS use this for any HTML content, never use code_comparison for HTML. goodFor: - showing before/after design improvements - comparing different CSS layout techniques (Flexbox vs Grid) - demonstrating responsive design changes - visual UI enhancements and styling improvements - whenToUse: Use when visual comparison is more important than code comparison. The slider layout is automatic for HTML content and provides an intuitive way to see design differences. + - comparing HTML structure and element placement + - showing semantic HTML differences (accessibility, attributes, form structure) + - demonstrating different approaches to the same markup + whenToUse: ALWAYS use this component for ANY HTML/CSS comparison, whether the focus is visual design or code structure. By default, it shows an interactive slider for visual comparisons. For code-focused comparisons (structure, semantics, attributes), add layout="side-by-side" with leftInitialMode="raw" and rightInitialMode="raw". avoid: - - when code comparison is more important than visual result - comparing completely different page structures (focus on similar layouts) rules: - CRITICAL - Must use exactly "---SEPARATOR---" on its own line to divide the two HTML blocks - type="html" is REQUIRED - Use descriptive leftLabel and rightLabel to describe each version - Both HTML blocks should represent comparable designs for best effect + - Use layout="side-by-side" with leftInitialMode="raw" and rightInitialMode="raw" when comparing code structure, not visual results - Can force layout="side-by-side" if you prefer code comparison over visual slider metadata: - type - Must be "html" - leftLabel - Recommended - Label for left panel (e.g. "Basic Design", "Flexbox") - rightLabel - Recommended - Label for right panel (e.g. "Enhanced", "CSS Grid") - layout - Optional - Use "side-by-side" to force code comparison instead of slider + - leftInitialMode - Optional - Set to "raw" to start left panel in code mode (defaults to "rendered") + - rightInitialMode - Optional - Set to "raw" to start right panel in code mode (defaults to "rendered") example: | ```comparison type="html" leftLabel="Basic Styles" rightLabel="Enhanced Design" @@ -192,6 +201,19 @@ components: ``` + --- Second Example --- + + ```comparison type="html" layout="side-by-side" leftLabel="Without Accessibility" rightLabel="With ARIA Attributes" leftInitialMode="raw" rightInitialMode="raw" + + + ---SEPARATOR--- + + + ``` - name: code_vs_rendered intendedUse: From eade6793d2581f393552548a26e497975084008b Mon Sep 17 00:00:00 2001 From: mfagundez Date: Wed, 31 Dec 2025 12:36:43 -0300 Subject: [PATCH 20/20] refactor(docs): update HTML/CSS comparison component info --- docs/explanatory_components.yml | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/explanatory_components.yml b/docs/explanatory_components.yml index 873eda26..8bbf13bf 100644 --- a/docs/explanatory_components.yml +++ b/docs/explanatory_components.yml @@ -58,18 +58,18 @@ components: - demonstrating bug fixes with validation - contrasting iterative vs functional approaches - showing different coding patterns or best practices - whenToUse: Use when comparing two versions of code enhances learning. Perfect for teaching refactoring, debugging, optimization, or comparing different solutions to the same problem. For HTML/CSS comparisons, prefer html_comparison which offers both visual and code comparison modes. + whenToUse: Use when comparing two versions of code enhances learning. Perfect for teaching refactoring, debugging, optimization, or comparing different solutions to the same problem. For HTML/CSS comparisons, prefer html_css_comparison which offers both visual and code comparison modes. avoid: - comparing unrelated code snippets - very long code blocks (keep focused on the key differences) - more than one concept per comparison - - HTML code (use html_comparison instead for better flexibility with raw/rendered modes) + - HTML code (use html_css_comparison instead for better flexibility with raw/rendered modes) rules: - - CRITICAL - NEVER use this for HTML code, always use html_comparison component instead + - CRITICAL - NEVER use this for HTML code, always use html_css_comparison component instead - CRITICAL - Must use exactly "---SEPARATOR---" on its own line to divide the two code blocks - type="code" is REQUIRED - language attribute is REQUIRED (e.g. language="python", language="javascript") - - NEVER set language="html" - use html_comparison component instead + - NEVER set language="html" - use html_css_comparison component instead - Use descriptive leftLabel and rightLabel to guide the student - Keep both code blocks similar in length for best visual results metadata: @@ -115,23 +115,24 @@ components: greet() // Hello, stranger! ``` - - name: html_comparison + - name: html_css_comparison intendedUse: - - comparing visual designs - - showing CSS improvements - - demonstrating UI changes with slider - - comparing HTML structure and semantics + - CSS styling comparisons + - visual design improvements + - HTML structure and semantics comparisons + - demonstrating CSS techniques description: >- - Compare two HTML/CSS implementations. This component handles both visual comparisons (using an interactive slider) and code structure comparisons (using side-by-side layout with raw mode). ALWAYS use this for any HTML content, never use code_comparison for HTML. + Compare two HTML/CSS implementations side-by-side. PRIMARY USE: comparing CSS styling, layout techniques, and visual designs. This is THE component for teaching CSS concepts through visual or code comparisons. Supports both interactive slider view (for visual CSS changes) and side-by-side code view (for CSS structure/syntax). ALWAYS use this for any HTML/CSS content, never use code_comparison for HTML. goodFor: - - showing before/after design improvements - - comparing different CSS layout techniques (Flexbox vs Grid) - - demonstrating responsive design changes - - visual UI enhancements and styling improvements + - CSS property comparisons (margin vs padding, flexbox vs grid) + - showing before/after styling improvements (basic CSS vs modern CSS) + - comparing CSS layout techniques (float vs flexbox, flexbox vs grid) + - demonstrating responsive design changes (mobile-first vs desktop-first) + - visual UI enhancements and styling improvements (colors, shadows, animations) + - CSS architecture patterns (BEM vs utility-first, CSS variables) - comparing HTML structure and element placement - showing semantic HTML differences (accessibility, attributes, form structure) - - demonstrating different approaches to the same markup - whenToUse: ALWAYS use this component for ANY HTML/CSS comparison, whether the focus is visual design or code structure. By default, it shows an interactive slider for visual comparisons. For code-focused comparisons (structure, semantics, attributes), add layout="side-by-side" with leftInitialMode="raw" and rightInitialMode="raw". + whenToUse: ALWAYS use this component for ANY HTML/CSS comparison in CSS courses, html courses, or UI design courses. By default shows an interactive slider for visual CSS comparisons. For code-focused comparisons (CSS syntax, selectors, structure), add layout="side-by-side" with leftInitialMode="raw" and rightInitialMode="raw". This is mandatory for HTML content - never use code_comparison for HTML/CSS. avoid: - comparing completely different page structures (focus on similar layouts) rules: