payload-quiz-plugin 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  A comprehensive quiz and test system plugin for [Payload CMS](https://payloadcms.com) v3. Create timed quizzes with multiple choice questions, automatic grading, and detailed results.
4
4
 
5
+ [![npm version](https://img.shields.io/npm/v/payload-quiz-plugin)](https://www.npmjs.com/package/payload-quiz-plugin)
6
+ [![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
7
+
5
8
  ## Features
6
9
 
7
10
  - **Collections**: Questions, Tests, and Certificate Types
@@ -402,15 +405,77 @@ export const Pages = {
402
405
 
403
406
  ## Styling
404
407
 
405
- The components use Tailwind CSS classes and are designed to work with shadcn/ui-style theming. They use CSS variables like:
408
+ The plugin ships with a CSS theme file that defines all quiz colors as CSS custom properties with sensible defaults and dark mode support.
409
+
410
+ ### Setup
411
+
412
+ Import the theme in your `globals.css`:
413
+
414
+ ```css
415
+ @import 'payload-quiz-plugin/styles';
416
+ ```
417
+
418
+ That's it. The import provides `:root` defaults and a `.dark` / `[data-theme="dark"]` override block. No Tailwind preset or `@theme` block is needed.
406
419
 
407
- - `--background`, `--foreground`
408
- - `--card`, `--card-foreground`
409
- - `--primary`, `--primary-foreground`
410
- - `--muted`, `--muted-foreground`
411
- - `--border`
420
+ ### Host project variables
421
+
422
+ Components also reference standard shadcn/ui-style variables (`--primary`, `--muted`, `--border`, etc.). Make sure your project defines those.
423
+
424
+ ### Overriding colors
425
+
426
+ Override any variable in your own CSS:
427
+
428
+ ```css
429
+ :root {
430
+ --quiz-success: oklch(0.72 0.19 160);
431
+ --quiz-error: hsl(25 95% 53%);
432
+ }
433
+ ```
412
434
 
413
- Make sure your project has these CSS variables defined.
435
+ For dark mode or multi-theme setups, scope overrides to the matching selector:
436
+
437
+ ```css
438
+ [data-theme="dark"] {
439
+ --quiz-success: oklch(0.55 0.15 150);
440
+ }
441
+
442
+ [data-theme="ocean"] {
443
+ --quiz-success: oklch(0.65 0.18 180);
444
+ }
445
+ ```
446
+
447
+ ### CSS variable reference
448
+
449
+ | Variable | Purpose |
450
+ |----------|---------|
451
+ | `--quiz-success` | Correct answer accent |
452
+ | `--quiz-success-foreground` | Text on success background |
453
+ | `--quiz-success-light` | Light success background |
454
+ | `--quiz-success-border` | Success border color |
455
+ | `--quiz-success-text` | Success text color |
456
+ | `--quiz-success-muted` | Muted success background |
457
+ | `--quiz-success-muted-border` | Muted success border |
458
+ | `--quiz-error` | Incorrect answer accent |
459
+ | `--quiz-error-foreground` | Text on error background |
460
+ | `--quiz-error-light` | Light error background |
461
+ | `--quiz-error-border` | Error border color |
462
+ | `--quiz-error-text` | Error text color |
463
+ | `--quiz-error-muted` | Muted error background |
464
+ | `--quiz-error-muted-border` | Muted error border |
465
+ | `--quiz-info` | Info badge / explanation accent |
466
+ | `--quiz-info-foreground` | Text on info background |
467
+ | `--quiz-info-light` | Light info background |
468
+ | `--quiz-info-border` | Info border color |
469
+ | `--quiz-info-text` | Info text color |
470
+ | `--quiz-warning-light` | Timer low-time background |
471
+ | `--quiz-warning-text` | Timer low-time text |
472
+ | `--quiz-choice-background` | Choice button background |
473
+ | `--quiz-choice-foreground` | Choice button text |
474
+ | `--quiz-choice-select` | Selected choice accent |
475
+ | `--quiz-choice-border` | Choice border color |
476
+ | `--quiz-choice-text` | Choice label text |
477
+ | `--quiz-choice-muted` | Unselected choice fill |
478
+ | `--quiz-choice-muted-border` | Choice indicator border |
414
479
 
415
480
  ## TypeScript
416
481
 
@@ -428,6 +493,12 @@ import type {
428
493
  } from 'payload-quiz-plugin'
429
494
  ```
430
495
 
496
+ ## Author
497
+
498
+ **Alexander Sedeke** - [@alexandrstudio](https://alexandr.studio)
499
+
500
+ Created for [Alexandr Studio](https://alexandr.studio)
501
+
431
502
  ## License
432
503
 
433
504
  This project is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/).
@@ -436,6 +507,13 @@ This project is licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/lic
436
507
  - **NonCommercial** — You may not use this for commercial purposes
437
508
  - **ShareAlike** — Derivatives must use the same license
438
509
 
510
+ ## Support
511
+
512
+ Need help?
513
+ - 📖 Check the [documentation](https://github.com/alexandrstudio/payload-quiz-plugin/wiki)
514
+ - 🐛 Found a bug? [Open an issue](https://github.com/alexandrstudio/payload-quiz-plugin/issues)
515
+ - 💬 Have questions? [Discussions](https://github.com/alexandrstudio/payload-quiz-plugin/discussions)
516
+
439
517
  ## Contributing
440
518
 
441
519
  Contributions are welcome! Please feel free to submit a Pull Request.
package/dist/client.d.mts CHANGED
@@ -205,11 +205,13 @@ declare function ChoiceOption({ id, text, isSelected, isMultiple, onClick, disab
205
205
  type Props = {
206
206
  result: QuizResult;
207
207
  testSlug: string;
208
+ /** Base route path for quiz pages (e.g. "/quiz", "/tests"). Defaults to "/tests" */
209
+ basePath?: string;
208
210
  className?: string;
209
211
  /** Optional custom component for rendering rich text explanations */
210
212
  RichTextComponent?: RichTextRenderer;
211
213
  };
212
- declare function QuizResults({ result, testSlug, className, RichTextComponent }: Props): react_jsx_runtime.JSX.Element;
214
+ declare function QuizResults({ result, testSlug, basePath, className, RichTextComponent, }: Props): react_jsx_runtime.JSX.Element;
213
215
 
214
216
  /**
215
217
  * Types for TestCard component
@@ -291,7 +293,7 @@ type TestCardProps = {
291
293
  * />
292
294
  * ```
293
295
  */
294
- declare function TestCard({ doc, title: titleFromProps, className, MediaComponent }: TestCardProps): react_jsx_runtime.JSX.Element;
296
+ declare function TestCard({ doc, title: titleFromProps, className, MediaComponent, }: TestCardProps): react_jsx_runtime.JSX.Element;
295
297
 
296
298
  /**
297
299
  * Utility function for merging Tailwind CSS classes
package/dist/client.d.ts CHANGED
@@ -205,11 +205,13 @@ declare function ChoiceOption({ id, text, isSelected, isMultiple, onClick, disab
205
205
  type Props = {
206
206
  result: QuizResult;
207
207
  testSlug: string;
208
+ /** Base route path for quiz pages (e.g. "/quiz", "/tests"). Defaults to "/tests" */
209
+ basePath?: string;
208
210
  className?: string;
209
211
  /** Optional custom component for rendering rich text explanations */
210
212
  RichTextComponent?: RichTextRenderer;
211
213
  };
212
- declare function QuizResults({ result, testSlug, className, RichTextComponent }: Props): react_jsx_runtime.JSX.Element;
214
+ declare function QuizResults({ result, testSlug, basePath, className, RichTextComponent, }: Props): react_jsx_runtime.JSX.Element;
213
215
 
214
216
  /**
215
217
  * Types for TestCard component
@@ -291,7 +293,7 @@ type TestCardProps = {
291
293
  * />
292
294
  * ```
293
295
  */
294
- declare function TestCard({ doc, title: titleFromProps, className, MediaComponent }: TestCardProps): react_jsx_runtime.JSX.Element;
296
+ declare function TestCard({ doc, title: titleFromProps, className, MediaComponent, }: TestCardProps): react_jsx_runtime.JSX.Element;
295
297
 
296
298
  /**
297
299
  * Utility function for merging Tailwind CSS classes
package/dist/client.js CHANGED
@@ -296,7 +296,7 @@ function QuizTimer({ timeRemaining, className }) {
296
296
  {
297
297
  className: cn(
298
298
  "flex items-center gap-2 px-4 py-2 rounded-lg font-mono text-lg font-medium",
299
- isLowTime ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-muted",
299
+ isLowTime ? "bg-[var(--quiz-warning-light)] text-[var(--quiz-warning-text)]" : "bg-muted",
300
300
  isCriticalTime && "animate-pulse",
301
301
  className
302
302
  ),
@@ -387,11 +387,11 @@ function ChoiceOption({
387
387
  onClick,
388
388
  disabled,
389
389
  className: cn(
390
- "w-full flex items-start gap-4 p-4 rounded-xl border text-left transition-all",
391
- isSelected ? "border-primary bg-primary/5" : "border-border bg-card hover:border-foreground/30",
390
+ "w-full text-left flex items-start gap-4 p-4 rounded-xl border transition-all cursor-pointer",
391
+ isSelected ? "border-[var(--quiz-choice-select)] bg-[var(--quiz-choice-select)]" : "border-[var(--quiz-choice-border)] hover:border-[var(--quiz-choice-select)]",
392
392
  disabled && "cursor-not-allowed opacity-60",
393
- showResult && isCorrect && "border-green-500 bg-green-50 dark:bg-green-900/20",
394
- showResult && isSelected && !isCorrect && "border-red-500 bg-red-50 dark:bg-red-900/20",
393
+ showResult && isCorrect && "border-[var(--quiz-success-border)] bg-[var(--quiz-success-light)]",
394
+ showResult && isSelected && !isCorrect && "border-[var(--quiz-error-border)] bg-[var(--quiz-error-light)]",
395
395
  className
396
396
  ),
397
397
  children: [
@@ -399,15 +399,15 @@ function ChoiceOption({
399
399
  "div",
400
400
  {
401
401
  className: cn(
402
- "shrink-0 size-6 rounded-full border-2 flex items-center justify-center",
402
+ "border size-6 rounded-full flex items-center justify-center bg-[var(--quiz-choice-muted-border)]",
403
403
  isSelected ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/30",
404
- showResult && isCorrect && "border-green-500 bg-green-500 text-white",
405
- showResult && isSelected && !isCorrect && "border-red-500 bg-red-500 text-white"
404
+ showResult && isCorrect && "border-[var(--quiz-success)] bg-[var(--quiz-success)] text-[var(--quiz-success-foreground)]",
405
+ showResult && isSelected && !isCorrect && "border-[var(--quiz-error)] bg-[var(--quiz-error)] text-[var(--quiz-error-foreground)]"
406
406
  ),
407
407
  children: (isSelected || showResult && isCorrect) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Check, { className: "size-4" })
408
408
  }
409
409
  ),
410
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex-1 text-base", children: text })
410
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 text-base", children: text })
411
411
  ]
412
412
  }
413
413
  );
@@ -441,13 +441,13 @@ function QuestionCard({
441
441
  {
442
442
  className: cn(
443
443
  "px-4 py-1 text-md font-normal rounded-xl",
444
- isMultiple ? "bg-blue-600 text-blue-700 dark:bg-blue-800 dark:text-blue-200" : "bg-green-600 text-green-200 dark:bg-green-800 dark:text-green-200"
444
+ isMultiple ? "bg-[var(--quiz-info)] text-[var(--quiz-info-foreground)]" : "bg-[var(--quiz-success)] text-[var(--quiz-success-foreground)]"
445
445
  ),
446
446
  children: isMultiple ? `Select ${requiredAnswers} answers` : "Select the best answer"
447
447
  }
448
448
  ) }),
449
449
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "prose dark:prose-invert max-w-none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "py-8 text-2xl font-medium leading-relaxed", children: question.question }) }),
450
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "space-y-3", children: question.choices?.map((choice) => {
450
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "space-y-3 flex flex-col gap-2", children: question.choices?.map((choice) => {
451
451
  const choiceId = choice.id || "";
452
452
  const isSelected = selectedChoiceIds.includes(choiceId);
453
453
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -470,8 +470,16 @@ var import_react2 = require("react");
470
470
  var import_lucide_react3 = require("lucide-react");
471
471
  var import_link = __toESM(require("next/link"));
472
472
  var import_jsx_runtime6 = require("react/jsx-runtime");
473
- function QuizResults({ result, testSlug, className, RichTextComponent }) {
474
- const [expandedQuestions, setExpandedQuestions] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
473
+ function QuizResults({
474
+ result,
475
+ testSlug,
476
+ basePath = "/tests",
477
+ className,
478
+ RichTextComponent
479
+ }) {
480
+ const [expandedQuestions, setExpandedQuestions] = (0, import_react2.useState)(
481
+ /* @__PURE__ */ new Set()
482
+ );
475
483
  const toggleQuestion = (questionId) => {
476
484
  const newExpanded = new Set(expandedQuestions);
477
485
  if (newExpanded.has(questionId)) {
@@ -492,17 +500,17 @@ function QuizResults({ result, testSlug, className, RichTextComponent }) {
492
500
  "div",
493
501
  {
494
502
  className: cn(
495
- "rounded-2xl p-8 text-center",
496
- result.passed ? "bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800" : "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800"
503
+ "rounded-2xl p-8 text-center border",
504
+ result.passed ? "bg-[var(--quiz-success-light)] border-[var(--quiz-success-muted-border)]" : "bg-[var(--quiz-error-light)] border-[var(--quiz-error-muted-border)]"
497
505
  ),
498
506
  children: [
499
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mb-4", children: result.passed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Trophy, { className: "size-16 mx-auto text-green-500" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Target, { className: "size-16 mx-auto text-red-500" }) }),
507
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mb-4", children: result.passed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Trophy, { className: "size-16 mx-auto text-[var(--quiz-success)]" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Target, { className: "size-16 mx-auto text-[var(--quiz-error)]" }) }),
500
508
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
501
509
  "h2",
502
510
  {
503
511
  className: cn(
504
512
  "text-3xl font-bold mb-2",
505
- result.passed ? "text-green-700 dark:text-green-400" : "text-red-700 dark:text-red-400"
513
+ result.passed ? "text-[var(--quiz-success-text)]" : "text-[var(--quiz-error-text)]"
506
514
  ),
507
515
  children: result.passed ? "Congratulations!" : "Keep Practicing!"
508
516
  }
@@ -526,7 +534,7 @@ function QuizResults({ result, testSlug, className, RichTextComponent }) {
526
534
  {
527
535
  className: cn(
528
536
  "stroke-current",
529
- result.passed ? "text-green-500" : "text-red-500"
537
+ result.passed ? "text-[var(--quiz-success)]" : "text-[var(--quiz-error)]"
530
538
  ),
531
539
  strokeWidth: "8",
532
540
  strokeLinecap: "round",
@@ -549,19 +557,19 @@ function QuizResults({ result, testSlug, className, RichTextComponent }) {
549
557
  ] })
550
558
  ] }),
551
559
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 max-w-2xl mx-auto", children: [
552
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-white dark:bg-card rounded-lg p-4", children: [
553
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-2xl font-bold text-green-600", children: result.correctAnswers }),
560
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-[var(--quiz-choice-background)] rounded-lg p-4", children: [
561
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-2xl font-bold text-[var(--quiz-success-text)]", children: result.correctAnswers }),
554
562
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-muted-foreground", children: "Correct" })
555
563
  ] }),
556
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-white dark:bg-card rounded-lg p-4", children: [
557
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-2xl font-bold text-red-600", children: result.incorrectAnswers }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-[var(--quiz-choice-background)] rounded-lg p-4", children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-2xl font-bold text-[var(--quiz-error-text)]", children: result.incorrectAnswers }),
558
566
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-muted-foreground", children: "Incorrect" })
559
567
  ] }),
560
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-white dark:bg-card rounded-lg p-4", children: [
568
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-[var(--quiz-choice-background)] rounded-lg p-4", children: [
561
569
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-2xl font-bold text-muted-foreground", children: result.unanswered }),
562
570
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-muted-foreground", children: "Unanswered" })
563
571
  ] }),
564
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-white dark:bg-card rounded-lg p-4", children: [
572
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "bg-[var(--quiz-choice-background)] rounded-lg p-4", children: [
565
573
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "text-2xl font-bold flex items-center justify-center gap-1", children: [
566
574
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Clock, { className: "size-5" }),
567
575
  formatTime(result.timeTaken)
@@ -576,7 +584,7 @@ function QuizResults({ result, testSlug, className, RichTextComponent }) {
576
584
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
577
585
  import_link.default,
578
586
  {
579
- href: `/tests/${testSlug}/start`,
587
+ href: `${basePath}/${testSlug}/start`,
580
588
  className: "inline-flex items-center justify-center gap-2 px-6 py-3 bg-primary text-primary-foreground font-medium rounded-xl hover:bg-primary/90 transition-colors",
581
589
  children: "Try Again"
582
590
  }
@@ -584,14 +592,14 @@ function QuizResults({ result, testSlug, className, RichTextComponent }) {
584
592
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
585
593
  import_link.default,
586
594
  {
587
- href: "/tests",
595
+ href: basePath,
588
596
  className: "inline-flex items-center justify-center gap-2 px-6 py-3 bg-muted text-foreground font-medium rounded-xl hover:bg-muted/80 transition-colors",
589
597
  children: "Back to Tests"
590
598
  }
591
599
  )
592
600
  ] }),
593
601
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-4", children: [
594
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "text-xl font-semibold", children: "Review Your Answers" }),
602
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "text-xl font-semibold mb-4", children: "Review Your Answers" }),
595
603
  result.questionResults.map((questionResult, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
596
604
  QuestionReview,
597
605
  {
@@ -613,82 +621,103 @@ function QuestionReview({
613
621
  onToggle,
614
622
  RichTextComponent
615
623
  }) {
616
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "border border-border rounded-xl overflow-hidden", children: [
617
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
618
- "button",
619
- {
620
- onClick: onToggle,
621
- className: cn(
622
- "w-full flex items-center gap-4 p-4 text-left transition-colors",
623
- isExpanded ? "bg-muted" : "bg-card hover:bg-muted/50"
624
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
625
+ "div",
626
+ {
627
+ className: cn(
628
+ "border border-border rounded-xl overflow-hidden",
629
+ isExpanded ? "bg-[var(--quiz-choice-background)]" : "bg-transparent"
630
+ ),
631
+ children: [
632
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
633
+ "button",
634
+ {
635
+ onClick: onToggle,
636
+ className: cn(
637
+ "w-full flex items-center gap-4 p-4 text-left transition-colors",
638
+ isExpanded ? "bg-[var(--quiz-choise-muted)]" : "bg-[var(--quiz-choice-background)] hover:bg-[var(--quiz-choise-muted)]"
639
+ ),
640
+ children: [
641
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
642
+ "div",
643
+ {
644
+ className: cn(
645
+ "flex-shrink-0 size-8 rounded-full flex items-center justify-center",
646
+ questionResult.isCorrect ? "bg-[var(--quiz-success-muted)] text-[var(--quiz-success-text)]" : "bg-[var(--quiz-error-muted)] text-[var(--quiz-error-text)]"
647
+ ),
648
+ children: questionResult.isCorrect ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Check, { className: "size-5" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.X, { className: "size-5" })
649
+ }
650
+ ),
651
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-1 min-w-0", children: [
652
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "text-sm text-muted-foreground mb-1", children: [
653
+ "Question ",
654
+ index + 1
655
+ ] }),
656
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "font-medium truncate", children: questionResult.question })
657
+ ] }),
658
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronUp, { className: "size-5 text-muted-foreground" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronDown, { className: "size-5 text-muted-foreground" })
659
+ ]
660
+ }
624
661
  ),
625
- children: [
626
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
662
+ isExpanded && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-4 border-t border-border space-y-4 ", children: [
663
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-4 mb-8 text-lg", children: questionResult.question }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-col gap-4", children: questionResult.choices.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
627
665
  "div",
628
666
  {
629
667
  className: cn(
630
- "flex-shrink-0 size-8 rounded-full flex items-center justify-center",
631
- questionResult.isCorrect ? "bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400" : "bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400"
668
+ "flex items-start gap-3 p-3 rounded-lg border",
669
+ choice.isCorrect && "bg-[var(--quiz-success-light)] border-[var(--quiz-success-muted-border)]",
670
+ choice.wasSelected && !choice.isCorrect && "bg-[var(--quiz-error-light)] border-[var(--quiz-error-muted-border)]",
671
+ !choice.isCorrect && !choice.wasSelected && "bg-transparent border-transparent"
632
672
  ),
633
- children: questionResult.isCorrect ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Check, { className: "size-5" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.X, { className: "size-5" })
634
- }
635
- ),
636
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-1 min-w-0", children: [
637
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "text-sm text-muted-foreground mb-1", children: [
638
- "Question ",
639
- index + 1
640
- ] }),
641
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "font-medium truncate", children: questionResult.question })
642
- ] }),
643
- isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronUp, { className: "size-5 text-muted-foreground" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronDown, { className: "size-5 text-muted-foreground" })
644
- ]
645
- }
646
- ),
647
- isExpanded && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-4 border-t border-border space-y-4", children: [
648
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-lg", children: questionResult.question }),
649
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "space-y-2", children: questionResult.choices.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
650
- "div",
651
- {
652
- className: cn(
653
- "flex items-start gap-3 p-3 rounded-lg border",
654
- choice.isCorrect && "bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800",
655
- choice.wasSelected && !choice.isCorrect && "bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800",
656
- !choice.isCorrect && !choice.wasSelected && "bg-muted/30 border-border"
657
- ),
658
- children: [
659
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
660
- "div",
661
- {
662
- className: cn(
663
- "flex-shrink-0 size-6 rounded-full flex items-center justify-center",
664
- choice.isCorrect && "bg-green-500 text-white",
665
- choice.wasSelected && !choice.isCorrect && "bg-red-500 text-white",
666
- !choice.isCorrect && !choice.wasSelected && "bg-muted"
673
+ children: [
674
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
675
+ "div",
676
+ {
677
+ className: cn(
678
+ "flex-shrink-0 size-6 rounded-full flex items-center justify-center",
679
+ choice.isCorrect && "bg-[var(--quiz-success)] text-[var(--quiz-success-foreground)]",
680
+ choice.wasSelected && !choice.isCorrect && "bg-[var(--quiz-error)] text-[var(--quiz-error-foreground)]",
681
+ !choice.isCorrect && !choice.wasSelected && "bg-[var(--quiz-choice-muted)]"
682
+ ),
683
+ children: [
684
+ choice.isCorrect && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Check, { className: "size-4" }),
685
+ choice.wasSelected && !choice.isCorrect && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.X, { className: "size-4" })
686
+ ]
687
+ }
667
688
  ),
668
- children: [
669
- choice.isCorrect && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Check, { className: "size-4" }),
670
- choice.wasSelected && !choice.isCorrect && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.X, { className: "size-4" })
671
- ]
689
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-1", children: [
690
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", children: [
691
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: choice.text }),
692
+ choice.wasSelected && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-xs px-2 py-0.5 bg-muted rounded-full text-nowrap", children: "Your answer" }),
693
+ choice.isCorrect && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-xs px-2 py-0.5 bg-[var(--quiz-success-muted)] text-[var(--quiz-success-text)] rounded-full", children: "Correct" })
694
+ ] }),
695
+ choice.explanation && RichTextComponent && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mt-2 text-sm text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
696
+ RichTextComponent,
697
+ {
698
+ data: choice.explanation,
699
+ enableGutter: false
700
+ }
701
+ ) })
702
+ ] })
703
+ ]
704
+ },
705
+ choice.id
706
+ )) }),
707
+ questionResult.explanation && RichTextComponent && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mt-4 p-4 bg-[var(--quiz-info-light)] rounded-lg border border-[var(--quiz-info-border)]", children: [
708
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "font-medium text-[var(--quiz-info-text)] mb-2", children: "Explanation" }),
709
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
710
+ RichTextComponent,
711
+ {
712
+ data: questionResult.explanation,
713
+ enableGutter: false
672
714
  }
673
- ),
674
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-1", children: [
675
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", children: [
676
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: choice.text }),
677
- choice.wasSelected && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-xs px-2 py-0.5 bg-muted rounded-full", children: "Your answer" }),
678
- choice.isCorrect && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-xs px-2 py-0.5 bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-400 rounded-full", children: "Correct" })
679
- ] }),
680
- choice.explanation && RichTextComponent && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mt-2 text-sm text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichTextComponent, { data: choice.explanation, enableGutter: false }) })
681
- ] })
682
- ]
683
- },
684
- choice.id
685
- )) }),
686
- questionResult.explanation && RichTextComponent && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800", children: [
687
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "font-medium text-blue-700 dark:text-blue-400 mb-2", children: "Explanation" }),
688
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichTextComponent, { data: questionResult.explanation, enableGutter: false }) })
689
- ] })
690
- ] })
691
- ] });
715
+ ) })
716
+ ] })
717
+ ] })
718
+ ]
719
+ }
720
+ );
692
721
  }
693
722
 
694
723
  // src/components/TestCard/TestCard.tsx
@@ -698,18 +727,28 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
698
727
  function DefaultMediaPlaceholder() {
699
728
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "h-48 bg-muted flex items-center justify-center" });
700
729
  }
701
- function TestCard({ doc, title: titleFromProps, className, MediaComponent }) {
730
+ function TestCard({
731
+ doc,
732
+ title: titleFromProps,
733
+ className,
734
+ MediaComponent
735
+ }) {
702
736
  const cardRef = (0, import_react3.useRef)(null);
703
737
  const linkRef = (0, import_react3.useRef)(null);
704
- const handleCardClick = (0, import_react3.useCallback)(
705
- (e) => {
706
- const target = e.target;
707
- if (target.tagName === "A" || target.tagName === "BUTTON") return;
708
- linkRef.current?.click();
709
- },
710
- []
711
- );
712
- const { slug, certificateType, meta, title, questionCount, timeLimit, passMark } = doc || {};
738
+ const handleCardClick = (0, import_react3.useCallback)((e) => {
739
+ const target = e.target;
740
+ if (target.tagName === "A" || target.tagName === "BUTTON") return;
741
+ linkRef.current?.click();
742
+ }, []);
743
+ const {
744
+ slug,
745
+ certificateType,
746
+ meta,
747
+ title,
748
+ questionCount,
749
+ timeLimit,
750
+ passMark
751
+ } = doc || {};
713
752
  const { description, image: metaImage } = meta || {};
714
753
  const titleToUse = titleFromProps || title;
715
754
  const sanitizedDescription = description?.replace(/\s/g, " ");
@@ -720,7 +759,7 @@ function TestCard({ doc, title: titleFromProps, className, MediaComponent }) {
720
759
  "article",
721
760
  {
722
761
  className: cn(
723
- "border border-border rounded-lg overflow-hidden bg-card hover:cursor-pointer",
762
+ "border border-border rounded-lg overflow-hidden bg-[var(--quiz-choice-background)] hover:cursor-pointer",
724
763
  className
725
764
  ),
726
765
  ref: cardRef,