Skip to content

Commit 9e28534

Browse files
committed
feat: implement tailwind validations and basic example
1 parent 33a0b72 commit 9e28534

File tree

6 files changed

+180
-28
lines changed

6 files changed

+180
-28
lines changed

lessons/10-tailwind-basics.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$schema": "../schemas/code-crispies-module-schema.json",
3+
"id": "tailwind-basics",
4+
"title": "Tailwind CSS Basics",
5+
"mode": "tailwind",
6+
"lessons": [
7+
{
8+
"id": "bg-colors",
9+
"title": "Background Colors",
10+
"description": "Learn to apply background colors using Tailwind utilities.",
11+
"task": "Add a blue background to the div using Tailwind classes.",
12+
"previewHTML": "<div class=\"{{USER_CLASSES}} p-8 rounded\">Hello Tailwind!</div>",
13+
"previewBaseCSS": "body { padding: 20px; font-family: sans-serif; }",
14+
"initialCode": "",
15+
"validations": [
16+
{
17+
"type": "contains_class",
18+
"value": "bg-blue-500",
19+
"message": "Add the 'bg-blue-500' class for a blue background."
20+
}
21+
],
22+
"sandboxCSS": "",
23+
"previewContainer": ""
24+
}
25+
],
26+
"description": "",
27+
"difficulty": "advanced"
28+
}

schemas/code-crispies-module-schema.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
"type": "string",
1818
"description": "Detailed description of the module content and purpose"
1919
},
20+
"mode": {
21+
"type": "string",
22+
"enum": ["css", "tailwind"],
23+
"description": "Whether this module teaches CSS or Tailwind"
24+
},
2025
"difficulty": {
2126
"type": "string",
2227
"enum": ["beginner", "intermediate", "advanced"],
@@ -53,6 +58,15 @@
5358
"type": "string",
5459
"description": "Detailed description of the lesson content and concepts"
5560
},
61+
"mode": {
62+
"type": "string",
63+
"enum": ["css", "tailwind"],
64+
"description": "Override module mode for individual lessons"
65+
},
66+
"tailwindConfig": {
67+
"type": "object",
68+
"description": "Custom Tailwind configuration if needed"
69+
},
5670
"task": {
5771
"type": "string",
5872
"description": "The specific task instructions for the student to complete"
@@ -91,7 +105,7 @@
91105
"properties": {
92106
"type": {
93107
"type": "string",
94-
"enum": ["contains", "not_contains", "regex", "property_value", "syntax", "custom"],
108+
"enum": ["contains", "contains_class", "not_contains", "regex", "property_value", "syntax", "custom"],
95109
"description": "Type of validation to perform"
96110
},
97111
"value": {

src/app.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,19 @@ function resetSuccessIndicators() {
210210
elements.runBtn.classList.remove("re-run");
211211
}
212212

213+
function updateEditorForMode(mode) {
214+
const codeInput = elements.codeInput;
215+
const editorLabel = document.querySelector(".editor-label");
216+
217+
if (mode === "tailwind") {
218+
codeInput.placeholder = "Enter Tailwind classes (e.g., bg-blue-500 text-white p-4)";
219+
if (editorLabel) editorLabel.textContent = "Tailwind Classes:";
220+
} else {
221+
codeInput.placeholder = "Enter your CSS code here...";
222+
if (editorLabel) editorLabel.textContent = "CSS Code:";
223+
}
224+
}
225+
213226
// Configure editor layout based on display type
214227
function resetEditorLayout(lesson) {
215228
elements.validationIndicators.innerHTML = "";
@@ -229,8 +242,12 @@ function loadCurrentLesson() {
229242
}
230243

231244
const lesson = state.currentModule.lessons[state.currentLessonIndex];
245+
const mode = lesson.mode || state.currentModule?.mode || "css";
232246
lessonEngine.setLesson(lesson);
233247

248+
// Update UI based on mode
249+
updateEditorForMode(mode);
250+
234251
// Reset any success indicators
235252
resetSuccessIndicators();
236253

src/config/lessons.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,24 @@ import carouselConfig from "../../lessons/02-css-only-carousel.json";
1111
// import selectorsConfig from "../../lessons/02-selectors.json";
1212
// import colorsConfig from "../../lessons/03-colors.json";
1313
// import typographyConfig from "../../lessons/04-typography.json";
14-
// import unitVariablesConfig from "../../lessons/05-units-variables.json";
14+
import unitVariablesConfig from "../../lessons/05-units-variables.json";
1515
// import transitionsAnimationsConfig from "../../lessons/06-transitions-animations.json";
1616
// import layoutConfig from "../../lessons/07-layouts.json";
1717
// import responsiveConfig from "../../lessons/08-responsive.json";
18+
import tailwindConfig from "../../lessons/10-tailwind-basics.json";
1819

1920
// Module store
2021
const moduleStore = [
2122
// basicsConfig,
2223
basicSelectorsConfig,
23-
// advancedSelectorsConfig,
24+
advancedSelectorsConfig,
25+
tailwindConfig
2426
// carouselConfig
2527
// boxModelConfig,
2628
// selectorsConfig,
2729
// colorsConfig
2830
// typographyConfig,
29-
// unitVariablesConfig,
31+
// unitVariablesConfig
3032
// transitionsAnimationsConfig,
3133
// layoutConfig,
3234
// responsiveConfig
@@ -37,8 +39,13 @@ const moduleStore = [
3739
* @returns {Promise<Array>} Promise resolving to array of modules
3840
*/
3941
export async function loadModules() {
40-
// In a real app, we might load these from a server
41-
return moduleStore;
42+
return moduleStore.map((module) => ({
43+
...module,
44+
lessons: module.lessons.map((lesson) => ({
45+
...lesson,
46+
mode: module.mode || "css"
47+
}))
48+
}));
4249
}
4350

4451
/**

src/helpers/validator.js

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,79 @@
22
* Validator - Functions to validate user CSS code
33
*/
44

5+
export function validateUserCode(userCode, lesson) {
6+
const mode = lesson.mode || "css";
7+
8+
if (mode === "tailwind") {
9+
return validateTailwindClasses(userCode, lesson);
10+
} else {
11+
return validateCssCode(userCode, lesson);
12+
}
13+
}
14+
15+
function validateTailwindClasses(userClasses, lesson) {
16+
if (!lesson || !lesson.validations) {
17+
return { isValid: true, message: "No validations specified for this lesson." };
18+
}
19+
20+
let result = {
21+
isValid: true,
22+
validCases: 0,
23+
totalCases: lesson.validations.length,
24+
message: "Your Tailwind classes look CRISPY!"
25+
};
26+
27+
for (const validation of lesson.validations) {
28+
const { type, value, message } = validation;
29+
let validationPassed = false;
30+
31+
switch (type) {
32+
case "contains_class":
33+
validationPassed = userClasses.split(/\s+/).includes(value);
34+
if (!validationPassed) {
35+
result = {
36+
...result,
37+
isValid: false,
38+
message: message || `Your classes should include "${value}".`
39+
};
40+
}
41+
break;
42+
43+
case "contains_pattern":
44+
const regex = new RegExp(value);
45+
validationPassed = regex.test(userClasses);
46+
if (!validationPassed) {
47+
result = {
48+
...result,
49+
isValid: false,
50+
message: message || "Your classes don't match the expected pattern."
51+
};
52+
}
53+
break;
54+
55+
default:
56+
// Fall back to original CSS validation for other types
57+
validationPassed = containsValidation(userClasses, value);
58+
}
59+
60+
if (validationPassed) {
61+
result.validCases++;
62+
} else {
63+
return result;
64+
}
65+
}
66+
67+
result.validCases = lesson.validations.length;
68+
return result;
69+
}
70+
571
/**
672
* Validate user CSS code against the lesson requirements
773
* @param {string} userCode - User submitted CSS code
874
* @param {Object} lesson - The current lesson object
975
* @returns {Object} Validation result with isValid and message properties
1076
*/
11-
export function validateUserCode(userCode, lesson) {
77+
export function validateCssCode(userCode, lesson) {
1278
if (!lesson || !lesson.validations) {
1379
return { isValid: true, message: "No validations specified for this lesson." };
1480
}

src/impl/LessonEngine.js

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -111,44 +111,64 @@ export class LessonEngine {
111111
renderPreview() {
112112
if (!this.currentLesson) return;
113113

114+
const mode = this.currentLesson.mode || this.currentModule?.mode || "css";
114115
const { previewHTML, previewBaseCSS, previewContainer, sandboxCSS } = this.currentLesson;
115116

116-
// Create an iframe for isolated preview rendering
117117
const iframe = document.createElement("iframe");
118118
iframe.style.width = "100%";
119119
iframe.style.height = "100%";
120120
iframe.style.border = "none";
121121
iframe.title = "Preview";
122122

123-
// Get the preview container
124123
const container = document.getElementById(previewContainer || "preview-area");
125-
126-
// Clear the container and add the iframe
127124
container.innerHTML = "";
128125
container.appendChild(iframe);
129126

130-
// Get the complete CSS by combining all parts
131-
const userCssWithWrapper = this.getCompleteCss();
132-
133-
// Write the content to the iframe
134127
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
135128
iframeDoc.open();
136-
iframeDoc.write(`
137-
<!DOCTYPE html>
138-
<html>
139-
<head>
140-
<style>${previewBaseCSS}</style>
141-
<style>${userCssWithWrapper}</style>
142-
<style>${sandboxCSS}</style>
143-
</head>
144-
<body>
145-
${previewHTML || "<div>No preview available</div>"}
146-
</body>
147-
</html>
148-
`);
129+
130+
if (mode === "tailwind") {
131+
// For Tailwind mode, user code goes directly in HTML classes
132+
const htmlWithClasses = this.injectTailwindClasses(previewHTML, this.userCode);
133+
iframeDoc.write(`
134+
<!DOCTYPE html>
135+
<html>
136+
<head>
137+
<script src="https://cdn.tailwindcss.com"></script>
138+
<style>${previewBaseCSS}</style>
139+
<style>${sandboxCSS}</style>
140+
</head>
141+
<body>
142+
${htmlWithClasses}
143+
</body>
144+
</html>
145+
`);
146+
} else {
147+
// Original CSS mode
148+
const userCssWithWrapper = this.getCompleteCss();
149+
iframeDoc.write(`
150+
<!DOCTYPE html>
151+
<html>
152+
<head>
153+
<style>${previewBaseCSS}</style>
154+
<style>${userCssWithWrapper}</style>
155+
<style>${sandboxCSS}</style>
156+
</head>
157+
<body>
158+
${previewHTML}
159+
</body>
160+
</html>
161+
`);
162+
}
163+
149164
iframeDoc.close();
150165
}
151166

167+
injectTailwindClasses(html, userClasses) {
168+
// Replace placeholder in HTML with user's Tailwind classes
169+
return html.replace(/{{USER_CLASSES}}/g, userClasses);
170+
}
171+
152172
/**
153173
* Validate user code against the current lesson's requirements
154174
* @returns {Object} Validation result

0 commit comments

Comments
 (0)