feat: implement tailwind validations and basic example

This commit is contained in:
2025-06-01 22:34:55 +02:00
parent 5872f04b32
commit 10fce10033
6 changed files with 180 additions and 28 deletions

View File

@@ -210,6 +210,19 @@ function resetSuccessIndicators() {
elements.runBtn.classList.remove("re-run");
}
function updateEditorForMode(mode) {
const codeInput = elements.codeInput;
const editorLabel = document.querySelector(".editor-label");
if (mode === "tailwind") {
codeInput.placeholder = "Enter Tailwind classes (e.g., bg-blue-500 text-white p-4)";
if (editorLabel) editorLabel.textContent = "Tailwind Classes:";
} else {
codeInput.placeholder = "Enter your CSS code here...";
if (editorLabel) editorLabel.textContent = "CSS Code:";
}
}
// Configure editor layout based on display type
function resetEditorLayout(lesson) {
elements.validationIndicators.innerHTML = "";
@@ -229,8 +242,12 @@ function loadCurrentLesson() {
}
const lesson = state.currentModule.lessons[state.currentLessonIndex];
const mode = lesson.mode || state.currentModule?.mode || "css";
lessonEngine.setLesson(lesson);
// Update UI based on mode
updateEditorForMode(mode);
// Reset any success indicators
resetSuccessIndicators();

View File

@@ -11,22 +11,24 @@ import carouselConfig from "../../lessons/02-css-only-carousel.json";
// import selectorsConfig from "../../lessons/02-selectors.json";
// import colorsConfig from "../../lessons/03-colors.json";
// import typographyConfig from "../../lessons/04-typography.json";
// import unitVariablesConfig from "../../lessons/05-units-variables.json";
import unitVariablesConfig from "../../lessons/05-units-variables.json";
// import transitionsAnimationsConfig from "../../lessons/06-transitions-animations.json";
// import layoutConfig from "../../lessons/07-layouts.json";
// import responsiveConfig from "../../lessons/08-responsive.json";
import tailwindConfig from "../../lessons/10-tailwind-basics.json";
// Module store
const moduleStore = [
// basicsConfig,
basicSelectorsConfig,
// advancedSelectorsConfig,
advancedSelectorsConfig,
tailwindConfig
// carouselConfig
// boxModelConfig,
// selectorsConfig,
// colorsConfig
// typographyConfig,
// unitVariablesConfig,
// unitVariablesConfig
// transitionsAnimationsConfig,
// layoutConfig,
// responsiveConfig
@@ -37,8 +39,13 @@ const moduleStore = [
* @returns {Promise<Array>} Promise resolving to array of modules
*/
export async function loadModules() {
// In a real app, we might load these from a server
return moduleStore;
return moduleStore.map((module) => ({
...module,
lessons: module.lessons.map((lesson) => ({
...lesson,
mode: module.mode || "css"
}))
}));
}
/**

View File

@@ -2,13 +2,79 @@
* Validator - Functions to validate user CSS code
*/
export function validateUserCode(userCode, lesson) {
const mode = lesson.mode || "css";
if (mode === "tailwind") {
return validateTailwindClasses(userCode, lesson);
} else {
return validateCssCode(userCode, lesson);
}
}
function validateTailwindClasses(userClasses, lesson) {
if (!lesson || !lesson.validations) {
return { isValid: true, message: "No validations specified for this lesson." };
}
let result = {
isValid: true,
validCases: 0,
totalCases: lesson.validations.length,
message: "Your Tailwind classes look CRISPY!"
};
for (const validation of lesson.validations) {
const { type, value, message } = validation;
let validationPassed = false;
switch (type) {
case "contains_class":
validationPassed = userClasses.split(/\s+/).includes(value);
if (!validationPassed) {
result = {
...result,
isValid: false,
message: message || `Your classes should include "${value}".`
};
}
break;
case "contains_pattern":
const regex = new RegExp(value);
validationPassed = regex.test(userClasses);
if (!validationPassed) {
result = {
...result,
isValid: false,
message: message || "Your classes don't match the expected pattern."
};
}
break;
default:
// Fall back to original CSS validation for other types
validationPassed = containsValidation(userClasses, value);
}
if (validationPassed) {
result.validCases++;
} else {
return result;
}
}
result.validCases = lesson.validations.length;
return result;
}
/**
* Validate user CSS code against the lesson requirements
* @param {string} userCode - User submitted CSS code
* @param {Object} lesson - The current lesson object
* @returns {Object} Validation result with isValid and message properties
*/
export function validateUserCode(userCode, lesson) {
export function validateCssCode(userCode, lesson) {
if (!lesson || !lesson.validations) {
return { isValid: true, message: "No validations specified for this lesson." };
}

View File

@@ -111,44 +111,64 @@ export class LessonEngine {
renderPreview() {
if (!this.currentLesson) return;
const mode = this.currentLesson.mode || this.currentModule?.mode || "css";
const { previewHTML, previewBaseCSS, previewContainer, sandboxCSS } = this.currentLesson;
// Create an iframe for isolated preview rendering
const iframe = document.createElement("iframe");
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "none";
iframe.title = "Preview";
// Get the preview container
const container = document.getElementById(previewContainer || "preview-area");
// Clear the container and add the iframe
container.innerHTML = "";
container.appendChild(iframe);
// Get the complete CSS by combining all parts
const userCssWithWrapper = this.getCompleteCss();
// Write the content to the iframe
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
iframeDoc.open();
iframeDoc.write(`
<!DOCTYPE html>
<html>
<head>
<style>${previewBaseCSS}</style>
<style>${userCssWithWrapper}</style>
<style>${sandboxCSS}</style>
</head>
<body>
${previewHTML || "<div>No preview available</div>"}
</body>
</html>
`);
if (mode === "tailwind") {
// For Tailwind mode, user code goes directly in HTML classes
const htmlWithClasses = this.injectTailwindClasses(previewHTML, this.userCode);
iframeDoc.write(`
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.tailwindcss.com"></script>
<style>${previewBaseCSS}</style>
<style>${sandboxCSS}</style>
</head>
<body>
${htmlWithClasses}
</body>
</html>
`);
} else {
// Original CSS mode
const userCssWithWrapper = this.getCompleteCss();
iframeDoc.write(`
<!DOCTYPE html>
<html>
<head>
<style>${previewBaseCSS}</style>
<style>${userCssWithWrapper}</style>
<style>${sandboxCSS}</style>
</head>
<body>
${previewHTML}
</body>
</html>
`);
}
iframeDoc.close();
}
injectTailwindClasses(html, userClasses) {
// Replace placeholder in HTML with user's Tailwind classes
return html.replace(/{{USER_CLASSES}}/g, userClasses);
}
/**
* Validate user code against the current lesson's requirements
* @returns {Object} Validation result