@superbuilders/incept-renderer 0.1.8 → 0.1.12
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 +537 -14
- package/dist/actions/index.d.ts +4 -1
- package/dist/actions/index.js +65 -6
- package/dist/actions/index.js.map +1 -1
- package/dist/components/index.d.ts +2 -3
- package/dist/components/index.js +43 -28
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +6 -11
- package/dist/index.js +94 -28
- package/dist/index.js.map +1 -1
- package/dist/parser-B8n3iHSM.d.ts +29 -0
- package/dist/qti-stimulus-renderer-CSuLfoff.d.ts +178 -0
- package/dist/{schema-DZoGAQdF.d.ts → schema-DKduufCs.d.ts} +106 -128
- package/dist/styles/duolingo.css +221 -0
- package/dist/styles/duolingo.css.map +1 -0
- package/dist/styles/duolingo.d.ts +2 -0
- package/dist/styles/themes.css +573 -37
- package/dist/styles/themes.css.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +5 -1
- package/dist/types-B7YRTQKt.d.ts +0 -102
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ A secure, server-driven QTI 3.0 assessment renderer for React/Next.js applicatio
|
|
|
12
12
|
- [Server Functions](#server-functions)
|
|
13
13
|
- [Client Components](#client-components)
|
|
14
14
|
- [Types](#types)
|
|
15
|
+
- [Rendering Stimuli](#rendering-stimuli)
|
|
15
16
|
- [Theming](#theming)
|
|
16
17
|
- [Complete Examples](#complete-examples)
|
|
17
18
|
- [Supported Interactions](#supported-interactions)
|
|
@@ -317,6 +318,29 @@ const result = await validateResponsesSecure(qtiXmlString, {
|
|
|
317
318
|
}
|
|
318
319
|
```
|
|
319
320
|
|
|
321
|
+
#### `parseAssessmentStimulusXml(xml: string)`
|
|
322
|
+
|
|
323
|
+
Parses QTI Assessment Stimulus XML (reading materials, passages).
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
|
|
327
|
+
|
|
328
|
+
const stimulus = parseAssessmentStimulusXml(stimulusXmlString)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Parameters:**
|
|
332
|
+
- `xml` (string) - Raw QTI 3.0 Assessment Stimulus XML string
|
|
333
|
+
|
|
334
|
+
**Returns:**
|
|
335
|
+
```tsx
|
|
336
|
+
{
|
|
337
|
+
identifier: string // Unique identifier from the XML
|
|
338
|
+
title: string // Human-readable title
|
|
339
|
+
xmlLang: string // Language code (e.g., "en")
|
|
340
|
+
bodyHtml: string // Sanitized HTML content ready for rendering
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
320
344
|
---
|
|
321
345
|
|
|
322
346
|
### Client Components
|
|
@@ -357,6 +381,26 @@ import { QTIRenderer } from "@superbuilders/incept-renderer"
|
|
|
357
381
|
| `responseFeedback` | `Record<string, { isCorrect: boolean; messageHtml?: string }>` | ❌ | Per-response feedback messages |
|
|
358
382
|
| `theme` | `"duolingo" \| "neobrutalist" \| string` | ❌ | Visual theme (default: `"duolingo"`) |
|
|
359
383
|
|
|
384
|
+
#### `<QTIStimulusRenderer />`
|
|
385
|
+
|
|
386
|
+
Renders QTI Assessment Stimuli (reading materials, passages).
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
import { QTIStimulusRenderer } from "@superbuilders/incept-renderer"
|
|
390
|
+
|
|
391
|
+
<QTIStimulusRenderer
|
|
392
|
+
stimulus={parsedStimulus}
|
|
393
|
+
className="my-8"
|
|
394
|
+
/>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Props:**
|
|
398
|
+
|
|
399
|
+
| Prop | Type | Required | Description |
|
|
400
|
+
|------|------|----------|-------------|
|
|
401
|
+
| `stimulus` | `AssessmentStimulus` | ✅ | Parsed stimulus from `parseAssessmentStimulusXml` |
|
|
402
|
+
| `className` | `string` | ❌ | Additional CSS classes for the container |
|
|
403
|
+
|
|
360
404
|
---
|
|
361
405
|
|
|
362
406
|
### Types
|
|
@@ -365,12 +409,15 @@ Import from `@superbuilders/incept-renderer`:
|
|
|
365
409
|
|
|
366
410
|
```tsx
|
|
367
411
|
import type {
|
|
412
|
+
// Assessment Items (questions)
|
|
368
413
|
DisplayItem,
|
|
369
414
|
DisplayBlock,
|
|
370
415
|
DisplayChoice,
|
|
371
416
|
DisplayChoiceInteraction,
|
|
372
417
|
FormShape,
|
|
373
|
-
ValidateResult
|
|
418
|
+
ValidateResult,
|
|
419
|
+
// Assessment Stimuli (reading materials)
|
|
420
|
+
AssessmentStimulus
|
|
374
421
|
} from "@superbuilders/incept-renderer"
|
|
375
422
|
```
|
|
376
423
|
|
|
@@ -435,17 +482,191 @@ interface ValidateResult {
|
|
|
435
482
|
}
|
|
436
483
|
```
|
|
437
484
|
|
|
485
|
+
#### `AssessmentStimulus`
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
interface AssessmentStimulus {
|
|
489
|
+
identifier: string // Unique identifier from the XML
|
|
490
|
+
title: string // Human-readable title
|
|
491
|
+
xmlLang: string // Language code (e.g., "en", "es")
|
|
492
|
+
bodyHtml: string // Sanitized HTML content
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Rendering Stimuli
|
|
499
|
+
|
|
500
|
+
In addition to assessment items (questions), the package supports rendering **Assessment Stimuli** — reading materials, passages, or articles that provide context for questions.
|
|
501
|
+
|
|
502
|
+
### What are Stimuli?
|
|
503
|
+
|
|
504
|
+
QTI Assessment Stimuli (`qti-assessment-stimulus`) are standalone content blocks that contain:
|
|
505
|
+
- Reading passages
|
|
506
|
+
- Articles with images
|
|
507
|
+
- Reference materials
|
|
508
|
+
- Any HTML content that accompanies questions
|
|
509
|
+
|
|
510
|
+
Unlike assessment items, stimuli have no interactions or correct answers — they're purely display content.
|
|
511
|
+
|
|
512
|
+
### Parsing Stimulus XML
|
|
513
|
+
|
|
514
|
+
Use `parseAssessmentStimulusXml` to parse stimulus XML:
|
|
515
|
+
|
|
516
|
+
```tsx
|
|
517
|
+
import { parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
|
|
518
|
+
|
|
519
|
+
const stimulusXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
520
|
+
<qti-assessment-stimulus
|
|
521
|
+
xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0"
|
|
522
|
+
identifier="stimulus-1"
|
|
523
|
+
xml:lang="en"
|
|
524
|
+
title="Biodiversity and Ecosystem Health">
|
|
525
|
+
<qti-stimulus-body>
|
|
526
|
+
<h2>Biodiversity and ecosystem health</h2>
|
|
527
|
+
<p><strong>Biodiversity</strong> is the variety of species in an ecosystem.</p>
|
|
528
|
+
<figure>
|
|
529
|
+
<img src="coral-reef.jpg" alt="A coral reef" />
|
|
530
|
+
<figcaption>Coral reef ecosystems have high biodiversity.</figcaption>
|
|
531
|
+
</figure>
|
|
532
|
+
</qti-stimulus-body>
|
|
533
|
+
</qti-assessment-stimulus>`
|
|
534
|
+
|
|
535
|
+
const stimulus = parseAssessmentStimulusXml(stimulusXml)
|
|
536
|
+
// Returns: { identifier, title, xmlLang, bodyHtml }
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Rendering Stimuli
|
|
540
|
+
|
|
541
|
+
Use the `QTIStimulusRenderer` component to render parsed stimuli:
|
|
542
|
+
|
|
543
|
+
```tsx
|
|
544
|
+
import { QTIStimulusRenderer, parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
|
|
545
|
+
|
|
546
|
+
function ReadingPassage({ xml }: { xml: string }) {
|
|
547
|
+
const stimulus = parseAssessmentStimulusXml(xml)
|
|
548
|
+
|
|
549
|
+
return (
|
|
550
|
+
<QTIStimulusRenderer
|
|
551
|
+
stimulus={stimulus}
|
|
552
|
+
className="my-8"
|
|
553
|
+
/>
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Server Action Pattern
|
|
559
|
+
|
|
560
|
+
For Next.js apps, create a server action to parse stimuli:
|
|
561
|
+
|
|
562
|
+
```tsx
|
|
563
|
+
// lib/qti-actions.ts
|
|
564
|
+
"use server"
|
|
565
|
+
|
|
566
|
+
import { parseAssessmentStimulusXml } from "@superbuilders/incept-renderer"
|
|
567
|
+
|
|
568
|
+
export async function parseStimulus(xml: string) {
|
|
569
|
+
return parseAssessmentStimulusXml(xml)
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
// app/reading/[id]/page.tsx
|
|
575
|
+
import { parseStimulus } from "@/lib/qti-actions"
|
|
576
|
+
import { QTIStimulusRenderer } from "@superbuilders/incept-renderer"
|
|
577
|
+
|
|
578
|
+
export default async function ReadingPage({ params }: { params: Promise<{ id: string }> }) {
|
|
579
|
+
const { id } = await params
|
|
580
|
+
const xml = await fetchStimulusXml(id) // Your data fetching
|
|
581
|
+
const stimulus = await parseStimulus(xml)
|
|
582
|
+
|
|
583
|
+
return <QTIStimulusRenderer stimulus={stimulus} />
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### Stimulus with Questions
|
|
588
|
+
|
|
589
|
+
A common pattern is showing a stimulus alongside related questions:
|
|
590
|
+
|
|
591
|
+
```tsx
|
|
592
|
+
import { QTIStimulusRenderer, QTIRenderer } from "@superbuilders/incept-renderer"
|
|
593
|
+
|
|
594
|
+
function QuestionWithPassage({ stimulus, item, ...props }) {
|
|
595
|
+
return (
|
|
596
|
+
<div className="grid grid-cols-2 gap-8">
|
|
597
|
+
{/* Reading passage on the left */}
|
|
598
|
+
<div className="overflow-y-auto max-h-[80vh]">
|
|
599
|
+
<QTIStimulusRenderer stimulus={stimulus} />
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
{/* Question on the right */}
|
|
603
|
+
<div>
|
|
604
|
+
<QTIRenderer item={item} {...props} />
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
)
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### AssessmentStimulus Type
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
interface AssessmentStimulus {
|
|
615
|
+
identifier: string // Unique ID from the XML
|
|
616
|
+
title: string // Human-readable title
|
|
617
|
+
xmlLang: string // Language code (e.g., "en")
|
|
618
|
+
bodyHtml: string // Sanitized HTML content
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Supported Content
|
|
623
|
+
|
|
624
|
+
The stimulus renderer supports:
|
|
625
|
+
- **Headings** (`h1`–`h6`)
|
|
626
|
+
- **Text formatting** (`p`, `strong`, `em`, `b`, `i`, `u`, `sub`, `sup`)
|
|
627
|
+
- **Lists** (`ul`, `ol`, `li`)
|
|
628
|
+
- **Images** (`img`, `figure`, `figcaption`)
|
|
629
|
+
- **Tables** (`table`, `thead`, `tbody`, `tr`, `th`, `td`)
|
|
630
|
+
- **Links** (`a`)
|
|
631
|
+
- **MathML** (mathematical expressions)
|
|
632
|
+
- **Collapsible sections** (`details`, `summary`)
|
|
633
|
+
- **Semantic elements** (`article`, `section`, `blockquote`, `cite`)
|
|
634
|
+
|
|
635
|
+
All content is sanitized to prevent XSS attacks while preserving safe HTML structure.
|
|
636
|
+
|
|
438
637
|
---
|
|
439
638
|
|
|
440
639
|
## Theming
|
|
441
640
|
|
|
442
|
-
The package includes two built-in themes and supports custom themes via CSS variables.
|
|
641
|
+
The package includes two built-in themes and supports custom themes via CSS variables. **You are not required to use the built-in styles** — you can fully customize the appearance to match your application's design system.
|
|
642
|
+
|
|
643
|
+
### Required CSS Imports
|
|
644
|
+
|
|
645
|
+
Import the base theme CSS in your app's root layout or global styles:
|
|
646
|
+
|
|
647
|
+
```tsx
|
|
648
|
+
// In your layout.tsx or globals.css
|
|
649
|
+
import "@superbuilders/incept-renderer/styles/themes.css"
|
|
650
|
+
```
|
|
443
651
|
|
|
444
652
|
### Built-in Themes
|
|
445
653
|
|
|
446
654
|
#### Duolingo (Default)
|
|
447
655
|
|
|
448
|
-
Clean, friendly design inspired by Duolingo's learning interface.
|
|
656
|
+
Clean, friendly design inspired by Duolingo's learning interface with a vibrant color palette.
|
|
657
|
+
|
|
658
|
+
**For the complete Duolingo experience**, import the Duolingo theme CSS which includes:
|
|
659
|
+
- Full Duolingo color palette (owl green, macaw blue, cardinal red, etc.)
|
|
660
|
+
- Rounded square choice indicators (A, B, C, D)
|
|
661
|
+
- Filled checkboxes with inset gap effect
|
|
662
|
+
- HTML content styling (lists, typography)
|
|
663
|
+
- Dark mode support
|
|
664
|
+
|
|
665
|
+
```tsx
|
|
666
|
+
// In your layout.tsx or globals.css
|
|
667
|
+
import "@superbuilders/incept-renderer/styles/themes.css"
|
|
668
|
+
import "@superbuilders/incept-renderer/styles/duolingo.css" // Full Duolingo styling
|
|
669
|
+
```
|
|
449
670
|
|
|
450
671
|
```tsx
|
|
451
672
|
<QTIRenderer theme="duolingo" ... />
|
|
@@ -461,19 +682,200 @@ Bold, high-contrast design with thick borders and sharp shadows.
|
|
|
461
682
|
|
|
462
683
|
### Custom Themes
|
|
463
684
|
|
|
464
|
-
Create your own theme by
|
|
685
|
+
Create your own theme by defining CSS variables for `[data-qti-theme="your-theme-name"]` in your stylesheet, then pass that theme name to the `QTIRenderer` component.
|
|
686
|
+
|
|
687
|
+
```tsx
|
|
688
|
+
<QTIRenderer theme="my-custom-theme" ... />
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### CSS Variables Reference
|
|
692
|
+
|
|
693
|
+
Below is the **complete list of CSS variables** you can override when creating a custom theme. All variables are optional — any variable you don't define will fall back to the default value.
|
|
694
|
+
|
|
695
|
+
#### Container Variables
|
|
696
|
+
|
|
697
|
+
Controls the main question container/card appearance.
|
|
698
|
+
|
|
699
|
+
| Variable | Description | Default |
|
|
700
|
+
|----------|-------------|---------|
|
|
701
|
+
| `--qti-container-bg` | Background color of the container | `var(--background, #ffffff)` |
|
|
702
|
+
| `--qti-container-border` | Border color of the container | `var(--border, #e5e5e5)` |
|
|
703
|
+
| `--qti-container-border-width` | Border thickness | `2px` |
|
|
704
|
+
| `--qti-container-radius` | Border radius (rounded corners) | `var(--radius, 0.5rem)` |
|
|
705
|
+
| `--qti-container-shadow` | Box shadow | `none` |
|
|
706
|
+
|
|
707
|
+
#### Typography Variables
|
|
708
|
+
|
|
709
|
+
Controls fonts and text styling throughout the renderer.
|
|
710
|
+
|
|
711
|
+
| Variable | Description | Default |
|
|
712
|
+
|----------|-------------|---------|
|
|
713
|
+
| `--qti-font-family` | Font family for all QTI text | `inherit` |
|
|
714
|
+
| `--qti-font-weight` | Base font weight | `500` |
|
|
715
|
+
| `--qti-tracking` | Letter spacing | `normal` |
|
|
716
|
+
|
|
717
|
+
#### Choice Variables
|
|
718
|
+
|
|
719
|
+
Controls the appearance of answer choices (radio buttons, checkboxes).
|
|
720
|
+
|
|
721
|
+
| Variable | Description | Default |
|
|
722
|
+
|----------|-------------|---------|
|
|
723
|
+
| `--qti-choice-bg` | Default background of choices | `var(--background, #ffffff)` |
|
|
724
|
+
| `--qti-choice-border` | Default border color of choices | `var(--accent, #f4f4f5)` |
|
|
725
|
+
| `--qti-choice-hover-bg` | Background on hover | `var(--muted, #f4f4f5)` |
|
|
726
|
+
| `--qti-choice-selected-bg` | Background when selected | `oklch(0.9 0.1 220 / 0.3)` |
|
|
727
|
+
| `--qti-choice-selected-border` | Border when selected | `oklch(0.6 0.2 250)` |
|
|
728
|
+
| `--qti-choice-correct-bg` | Background for correct answers | `oklch(0.9 0.15 140 / 0.3)` |
|
|
729
|
+
| `--qti-choice-correct-border` | Border for correct answers | `#22c55e` |
|
|
730
|
+
| `--qti-choice-incorrect-bg` | Background for incorrect answers | `oklch(0.9 0.1 25 / 0.3)` |
|
|
731
|
+
| `--qti-choice-incorrect-border` | Border for incorrect answers | `#ef4444` |
|
|
732
|
+
|
|
733
|
+
#### Button Variables
|
|
734
|
+
|
|
735
|
+
Controls the appearance of buttons (if using the QTI button classes).
|
|
736
|
+
|
|
737
|
+
| Variable | Description | Default |
|
|
738
|
+
|----------|-------------|---------|
|
|
739
|
+
| `--qti-button-bg` | Button background color | `var(--primary, #18181b)` |
|
|
740
|
+
| `--qti-button-text` | Button text color | `var(--primary-foreground, #ffffff)` |
|
|
741
|
+
| `--qti-button-border` | Button border color | `var(--primary, #18181b)` |
|
|
742
|
+
| `--qti-button-shadow` | Button box shadow | `none` |
|
|
743
|
+
| `--qti-button-radius` | Button border radius | `var(--radius, 0.5rem)` |
|
|
744
|
+
| `--qti-button-font-weight` | Button font weight | `700` |
|
|
745
|
+
|
|
746
|
+
#### Feedback Variables
|
|
747
|
+
|
|
748
|
+
Controls the appearance of feedback messages shown after validation.
|
|
749
|
+
|
|
750
|
+
| Variable | Description | Default |
|
|
751
|
+
|----------|-------------|---------|
|
|
752
|
+
| `--qti-feedback-bg` | Feedback container background | `var(--background, #ffffff)` |
|
|
753
|
+
| `--qti-feedback-border` | Feedback container border | `var(--border, #e5e5e5)` |
|
|
754
|
+
| `--qti-feedback-correct-text` | Text color for correct feedback | `#22c55e` |
|
|
755
|
+
| `--qti-feedback-correct-icon-bg` | Icon background for correct | `#22c55e` |
|
|
756
|
+
| `--qti-feedback-incorrect-text` | Text color for incorrect feedback | `#ef4444` |
|
|
757
|
+
| `--qti-feedback-incorrect-icon-bg` | Icon background for incorrect | `#ef4444` |
|
|
758
|
+
|
|
759
|
+
#### Input Variables (Text Entry, Textarea, Dropdowns)
|
|
760
|
+
|
|
761
|
+
Controls the appearance of text input fields, textareas, and select/dropdown elements.
|
|
762
|
+
|
|
763
|
+
| Variable | Description | Default |
|
|
764
|
+
|----------|-------------|---------|
|
|
765
|
+
| `--qti-input-bg` | Input background color | `var(--background, #ffffff)` |
|
|
766
|
+
| `--qti-input-border` | Input border color | `var(--border, #e5e5e5)` |
|
|
767
|
+
| `--qti-input-border-width` | Input border thickness | `2px` |
|
|
768
|
+
| `--qti-input-radius` | Input border radius | `var(--radius, 0.5rem)` |
|
|
769
|
+
| `--qti-input-shadow` | Input box shadow | `none` |
|
|
770
|
+
| `--qti-input-focus-border` | Border color when focused | `var(--ring, #2563eb)` |
|
|
771
|
+
| `--qti-input-focus-shadow` | Box shadow when focused | `0 0 0 2px rgba(37, 99, 235, 0.2)` |
|
|
772
|
+
| `--qti-input-correct-border` | Border for correct text answers | `#22c55e` |
|
|
773
|
+
| `--qti-input-incorrect-border` | Border for incorrect text answers | `#ef4444` |
|
|
774
|
+
|
|
775
|
+
#### Choice Indicator Variables (Radio/Checkbox Circles)
|
|
776
|
+
|
|
777
|
+
Controls the appearance of the radio button and checkbox indicators.
|
|
778
|
+
|
|
779
|
+
| Variable | Description | Default |
|
|
780
|
+
|----------|-------------|---------|
|
|
781
|
+
| `--qti-indicator-size` | Size of the indicator | `1.5rem` |
|
|
782
|
+
| `--qti-indicator-bg` | Default background | `var(--background, #ffffff)` |
|
|
783
|
+
| `--qti-indicator-border` | Default border color | `var(--border, #d4d4d8)` |
|
|
784
|
+
| `--qti-indicator-border-width` | Border thickness | `2px` |
|
|
785
|
+
| `--qti-indicator-radius` | Border radius (use `9999px` for circles, `0` for squares) | `9999px` |
|
|
786
|
+
| `--qti-indicator-checked-bg` | Background when checked | `var(--primary, #18181b)` |
|
|
787
|
+
| `--qti-indicator-checked-border` | Border when checked | `var(--primary, #18181b)` |
|
|
788
|
+
| `--qti-indicator-checked-text` | Checkmark/dot color | `var(--primary-foreground, #ffffff)` |
|
|
789
|
+
|
|
790
|
+
#### Progress Bar Variables
|
|
791
|
+
|
|
792
|
+
Controls the appearance of progress indicators.
|
|
793
|
+
|
|
794
|
+
| Variable | Description | Default |
|
|
795
|
+
|----------|-------------|---------|
|
|
796
|
+
| `--qti-progress-bg` | Progress bar background (empty part) | `var(--muted, #f4f4f5)` |
|
|
797
|
+
| `--qti-progress-fill` | Progress bar fill color | `var(--primary, #18181b)` |
|
|
798
|
+
| `--qti-progress-height` | Height of the progress bar | `0.5rem` |
|
|
799
|
+
| `--qti-progress-radius` | Border radius | `var(--radius, 0.5rem)` |
|
|
800
|
+
| `--qti-progress-border` | Border color | `transparent` |
|
|
801
|
+
| `--qti-progress-border-width` | Border thickness | `0px` |
|
|
802
|
+
|
|
803
|
+
#### Content Variables (Question Text, Prompts)
|
|
804
|
+
|
|
805
|
+
Controls the appearance of HTML content (question text, prompts, instructions).
|
|
806
|
+
|
|
807
|
+
| Variable | Description | Default |
|
|
808
|
+
|----------|-------------|---------|
|
|
809
|
+
| `--qti-content-font-size` | Font size for content | `1.125rem` |
|
|
810
|
+
| `--qti-content-line-height` | Line height for content | `1.625` |
|
|
811
|
+
| `--qti-content-font-weight` | Font weight for content | `500` |
|
|
812
|
+
|
|
813
|
+
#### Image Grid Variables
|
|
814
|
+
|
|
815
|
+
Controls the layout of image-based choice interactions.
|
|
816
|
+
|
|
817
|
+
| Variable | Description | Default |
|
|
818
|
+
|----------|-------------|---------|
|
|
819
|
+
| `--qti-grid-gap` | Gap between grid items | `1rem` |
|
|
820
|
+
| `--qti-image-card-min-height` | Minimum height of image cards | `180px` |
|
|
821
|
+
| `--qti-image-card-padding` | Padding inside image cards | `1rem` |
|
|
822
|
+
| `--qti-image-max-height` | Maximum height of images | `160px` |
|
|
823
|
+
|
|
824
|
+
#### Order Interaction Variables (Drag-and-Drop Sorting)
|
|
825
|
+
|
|
826
|
+
Controls the appearance of the order interaction container that holds draggable items.
|
|
827
|
+
|
|
828
|
+
| Variable | Description | Default |
|
|
829
|
+
|----------|-------------|---------|
|
|
830
|
+
| `--qti-order-container-bg` | Container background | `var(--muted, #f4f4f5)` |
|
|
831
|
+
| `--qti-order-container-border` | Container border color | `transparent` |
|
|
832
|
+
| `--qti-order-container-border-width` | Container border thickness | `2px` |
|
|
833
|
+
| `--qti-order-container-radius` | Container border radius | `0.75rem` |
|
|
834
|
+
| `--qti-order-container-padding` | Container padding | `1rem` |
|
|
835
|
+
| `--qti-order-container-gap` | Gap between drag items | `0.5rem` |
|
|
836
|
+
| `--qti-order-container-correct-bg` | Background when correct | `oklch(0.9 0.15 140 / 0.2)` |
|
|
837
|
+
| `--qti-order-container-correct-border` | Border when correct | `#22c55e` |
|
|
838
|
+
| `--qti-order-container-incorrect-bg` | Background when incorrect | `oklch(0.9 0.1 25 / 0.2)` |
|
|
839
|
+
| `--qti-order-container-incorrect-border` | Border when incorrect | `#ef4444` |
|
|
840
|
+
|
|
841
|
+
#### Order Item Variables (Drag Tokens)
|
|
842
|
+
|
|
843
|
+
Controls the appearance of individual draggable items/tokens.
|
|
844
|
+
|
|
845
|
+
| Variable | Description | Default |
|
|
846
|
+
|----------|-------------|---------|
|
|
847
|
+
| `--qti-order-item-bg` | Item background | `var(--background, #ffffff)` |
|
|
848
|
+
| `--qti-order-item-border` | Item border color | `var(--border, #e5e5e5)` |
|
|
849
|
+
| `--qti-order-item-border-width` | Item border thickness | `1px` |
|
|
850
|
+
| `--qti-order-item-radius` | Item border radius | `0.5rem` |
|
|
851
|
+
| `--qti-order-item-padding` | Item padding | `1rem` |
|
|
852
|
+
| `--qti-order-item-shadow` | Item box shadow | `0 1px 2px rgba(0, 0, 0, 0.05)` |
|
|
853
|
+
| `--qti-order-item-hover-border` | Border on hover | `oklch(0.6 0.2 250)` |
|
|
854
|
+
| `--qti-order-item-hover-shadow` | Shadow on hover | `0 4px 6px rgba(0, 0, 0, 0.1)` |
|
|
855
|
+
| `--qti-order-item-dragging-border` | Border while dragging | `oklch(0.6 0.2 250)` |
|
|
856
|
+
| `--qti-order-item-dragging-shadow` | Shadow while dragging | `0 8px 16px rgba(0, 0, 0, 0.15)` |
|
|
857
|
+
| `--qti-order-item-handle-color` | Drag handle icon color | `var(--muted-foreground, #71717a)` |
|
|
858
|
+
|
|
859
|
+
### Complete Custom Theme Example
|
|
860
|
+
|
|
861
|
+
Here's a complete example defining all variables for a custom theme:
|
|
465
862
|
|
|
466
863
|
```css
|
|
467
|
-
/* In your globals.css */
|
|
864
|
+
/* In your globals.css or styles file */
|
|
468
865
|
[data-qti-theme="my-custom-theme"] {
|
|
469
|
-
/* Container
|
|
866
|
+
/* ========== Container ========== */
|
|
470
867
|
--qti-container-bg: #ffffff;
|
|
471
868
|
--qti-container-border: #e0e0e0;
|
|
472
869
|
--qti-container-border-width: 1px;
|
|
473
870
|
--qti-container-radius: 12px;
|
|
474
871
|
--qti-container-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
475
872
|
|
|
476
|
-
/*
|
|
873
|
+
/* ========== Typography ========== */
|
|
874
|
+
--qti-font-family: "Inter", system-ui, sans-serif;
|
|
875
|
+
--qti-font-weight: 400;
|
|
876
|
+
--qti-tracking: normal;
|
|
877
|
+
|
|
878
|
+
/* ========== Choices (Radio/Checkbox Cards) ========== */
|
|
477
879
|
--qti-choice-bg: #f8f8f8;
|
|
478
880
|
--qti-choice-border: #d0d0d0;
|
|
479
881
|
--qti-choice-hover-bg: #f0f0f0;
|
|
@@ -484,30 +886,151 @@ Create your own theme by overriding CSS variables:
|
|
|
484
886
|
--qti-choice-incorrect-bg: #ffebee;
|
|
485
887
|
--qti-choice-incorrect-border: #f44336;
|
|
486
888
|
|
|
487
|
-
/*
|
|
889
|
+
/* ========== Buttons ========== */
|
|
488
890
|
--qti-button-bg: #2196f3;
|
|
489
891
|
--qti-button-text: #ffffff;
|
|
490
892
|
--qti-button-border: transparent;
|
|
491
893
|
--qti-button-shadow: none;
|
|
894
|
+
--qti-button-radius: 8px;
|
|
895
|
+
--qti-button-font-weight: 600;
|
|
492
896
|
|
|
493
|
-
/*
|
|
494
|
-
--qti-font-family: system-ui, sans-serif;
|
|
495
|
-
--qti-font-weight: 400;
|
|
496
|
-
|
|
497
|
-
/* Feedback styling */
|
|
897
|
+
/* ========== Feedback Messages ========== */
|
|
498
898
|
--qti-feedback-bg: #ffffff;
|
|
499
899
|
--qti-feedback-border: #e0e0e0;
|
|
500
900
|
--qti-feedback-correct-text: #2e7d32;
|
|
901
|
+
--qti-feedback-correct-icon-bg: #4caf50;
|
|
501
902
|
--qti-feedback-incorrect-text: #c62828;
|
|
903
|
+
--qti-feedback-incorrect-icon-bg: #f44336;
|
|
904
|
+
|
|
905
|
+
/* ========== Text Inputs / Textareas / Dropdowns ========== */
|
|
906
|
+
--qti-input-bg: #ffffff;
|
|
907
|
+
--qti-input-border: #d0d0d0;
|
|
908
|
+
--qti-input-border-width: 1px;
|
|
909
|
+
--qti-input-radius: 8px;
|
|
910
|
+
--qti-input-shadow: none;
|
|
911
|
+
--qti-input-focus-border: #2196f3;
|
|
912
|
+
--qti-input-focus-shadow: 0 0 0 3px rgba(33, 150, 243, 0.2);
|
|
913
|
+
--qti-input-correct-border: #4caf50;
|
|
914
|
+
--qti-input-incorrect-border: #f44336;
|
|
915
|
+
|
|
916
|
+
/* ========== Choice Indicators (Radio/Checkbox Circles) ========== */
|
|
917
|
+
--qti-indicator-size: 1.5rem;
|
|
918
|
+
--qti-indicator-bg: #ffffff;
|
|
919
|
+
--qti-indicator-border: #d0d0d0;
|
|
920
|
+
--qti-indicator-border-width: 2px;
|
|
921
|
+
--qti-indicator-radius: 9999px; /* Use 0 for square checkboxes */
|
|
922
|
+
--qti-indicator-checked-bg: #2196f3;
|
|
923
|
+
--qti-indicator-checked-border: #2196f3;
|
|
924
|
+
--qti-indicator-checked-text: #ffffff;
|
|
925
|
+
|
|
926
|
+
/* ========== Progress Bar ========== */
|
|
927
|
+
--qti-progress-bg: #e0e0e0;
|
|
928
|
+
--qti-progress-fill: #2196f3;
|
|
929
|
+
--qti-progress-height: 8px;
|
|
930
|
+
--qti-progress-radius: 4px;
|
|
931
|
+
--qti-progress-border: transparent;
|
|
932
|
+
--qti-progress-border-width: 0;
|
|
933
|
+
|
|
934
|
+
/* ========== Content (Question Text) ========== */
|
|
935
|
+
--qti-content-font-size: 1.125rem;
|
|
936
|
+
--qti-content-line-height: 1.6;
|
|
937
|
+
--qti-content-font-weight: 400;
|
|
938
|
+
|
|
939
|
+
/* ========== Image Grids ========== */
|
|
940
|
+
--qti-grid-gap: 1rem;
|
|
941
|
+
--qti-image-card-min-height: 180px;
|
|
942
|
+
--qti-image-card-padding: 1rem;
|
|
943
|
+
--qti-image-max-height: 160px;
|
|
944
|
+
|
|
945
|
+
/* ========== Order Interaction (Drag-and-Drop Container) ========== */
|
|
946
|
+
--qti-order-container-bg: #f5f5f5;
|
|
947
|
+
--qti-order-container-border: #e0e0e0;
|
|
948
|
+
--qti-order-container-border-width: 1px;
|
|
949
|
+
--qti-order-container-radius: 12px;
|
|
950
|
+
--qti-order-container-padding: 1rem;
|
|
951
|
+
--qti-order-container-gap: 0.75rem;
|
|
952
|
+
--qti-order-container-correct-bg: rgba(76, 175, 80, 0.15);
|
|
953
|
+
--qti-order-container-correct-border: #4caf50;
|
|
954
|
+
--qti-order-container-incorrect-bg: rgba(244, 67, 54, 0.15);
|
|
955
|
+
--qti-order-container-incorrect-border: #f44336;
|
|
956
|
+
|
|
957
|
+
/* ========== Order Items (Drag Tokens) ========== */
|
|
958
|
+
--qti-order-item-bg: #ffffff;
|
|
959
|
+
--qti-order-item-border: #e0e0e0;
|
|
960
|
+
--qti-order-item-border-width: 1px;
|
|
961
|
+
--qti-order-item-radius: 8px;
|
|
962
|
+
--qti-order-item-padding: 1rem;
|
|
963
|
+
--qti-order-item-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
964
|
+
--qti-order-item-hover-border: #2196f3;
|
|
965
|
+
--qti-order-item-hover-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
|
|
966
|
+
--qti-order-item-dragging-border: #2196f3;
|
|
967
|
+
--qti-order-item-dragging-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
|
968
|
+
--qti-order-item-handle-color: #9e9e9e;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/* Optional: Dark mode support for your custom theme */
|
|
972
|
+
.dark[data-qti-theme="my-custom-theme"],
|
|
973
|
+
[data-qti-theme="my-custom-theme"] .dark {
|
|
974
|
+
/* Container */
|
|
975
|
+
--qti-container-bg: #1a1a1a;
|
|
976
|
+
--qti-container-border: #333333;
|
|
977
|
+
|
|
978
|
+
/* Choices */
|
|
979
|
+
--qti-choice-bg: #252525;
|
|
980
|
+
--qti-choice-border: #444444;
|
|
981
|
+
--qti-choice-hover-bg: #333333;
|
|
982
|
+
--qti-choice-selected-bg: rgba(33, 150, 243, 0.2);
|
|
983
|
+
--qti-choice-selected-border: #2196f3;
|
|
984
|
+
--qti-choice-correct-bg: rgba(76, 175, 80, 0.2);
|
|
985
|
+
--qti-choice-incorrect-bg: rgba(244, 67, 54, 0.2);
|
|
986
|
+
|
|
987
|
+
/* Feedback */
|
|
988
|
+
--qti-feedback-bg: #1a1a1a;
|
|
989
|
+
--qti-feedback-border: #333333;
|
|
990
|
+
|
|
991
|
+
/* Inputs */
|
|
992
|
+
--qti-input-bg: #252525;
|
|
993
|
+
--qti-input-border: #444444;
|
|
994
|
+
|
|
995
|
+
/* Indicators */
|
|
996
|
+
--qti-indicator-bg: #252525;
|
|
997
|
+
--qti-indicator-border: #444444;
|
|
998
|
+
|
|
999
|
+
/* Progress */
|
|
1000
|
+
--qti-progress-bg: #333333;
|
|
1001
|
+
|
|
1002
|
+
/* Order Interaction */
|
|
1003
|
+
--qti-order-container-bg: #252525;
|
|
1004
|
+
--qti-order-container-border: #444444;
|
|
1005
|
+
--qti-order-container-correct-bg: rgba(76, 175, 80, 0.2);
|
|
1006
|
+
--qti-order-container-incorrect-bg: rgba(244, 67, 54, 0.2);
|
|
1007
|
+
|
|
1008
|
+
/* Order Items */
|
|
1009
|
+
--qti-order-item-bg: #1a1a1a;
|
|
1010
|
+
--qti-order-item-border: #444444;
|
|
1011
|
+
--qti-order-item-handle-color: #888888;
|
|
502
1012
|
}
|
|
503
1013
|
```
|
|
504
1014
|
|
|
505
|
-
Then use
|
|
1015
|
+
Then use your custom theme:
|
|
506
1016
|
|
|
507
1017
|
```tsx
|
|
508
1018
|
<QTIRenderer theme="my-custom-theme" ... />
|
|
509
1019
|
```
|
|
510
1020
|
|
|
1021
|
+
### Partial Overrides
|
|
1022
|
+
|
|
1023
|
+
You don't need to define all variables. You can create a theme that only overrides specific values:
|
|
1024
|
+
|
|
1025
|
+
```css
|
|
1026
|
+
/* Minimal custom theme - only changes colors */
|
|
1027
|
+
[data-qti-theme="brand-colors"] {
|
|
1028
|
+
--qti-choice-selected-bg: #your-brand-color-light;
|
|
1029
|
+
--qti-choice-selected-border: #your-brand-color;
|
|
1030
|
+
--qti-button-bg: #your-brand-color;
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
511
1034
|
---
|
|
512
1035
|
|
|
513
1036
|
## Complete Examples
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
export { a as parseAssessmentStimulusXml } from '../parser-B8n3iHSM.js';
|
|
2
|
+
import { k as DisplayItem, m as FormShape, V as ValidateResult } from '../schema-DKduufCs.js';
|
|
3
|
+
export { a as AssessmentStimulus } from '../schema-DKduufCs.js';
|
|
4
|
+
import 'zod';
|
|
2
5
|
|
|
3
6
|
declare function buildDisplayModelFromXml(qtiXml: string): {
|
|
4
7
|
itemKey: string;
|