@rlx-ui/mcp 0.0.16 → 0.0.17

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.
@@ -1432,6 +1432,32 @@
1432
1432
  }
1433
1433
  ]
1434
1434
  },
1435
+ {
1436
+ "name": "Quiz Box",
1437
+ "exportName": "QuizBox",
1438
+ "packageName": "@rlx-components/quiz-box",
1439
+ "version": "0.0.1",
1440
+ "slug": "quiz-box",
1441
+ "category": "component",
1442
+ "sourceCode": "\"use client\";\n\nimport { Button } from \"@rlx-widgets/button\";\nimport { CheckCircle2, XCircle } from \"lucide-react\";\nimport { cn } from \"@rlx-widgets/base\";\nimport { ConfettiOverlay, QuizOptionButton } from \"./components\";\nimport { ConfettiPiece, QuizBoxProps } from \"./types\";\nimport { generateConfettis, getMessage } from \"./utils\";\nimport { Pill } from \"./components\";\nimport { useEffect, useState } from \"react\";\n\nexport function QuizBox({ quizzes }: QuizBoxProps) {\n const [currentIndex, setCurrentIndex] = useState<number>(0);\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [finished, setFinished] = useState<boolean>(false);\n const [numCorrect, setNumCorrect] = useState<number>(0);\n const [showConfetti, setShowConfetti] = useState<boolean>(false);\n const [confettiPieces, setConfettiPieces] = useState<Array<ConfettiPiece>>(\n []\n );\n const [correctAnswerFound, setCorrectAnswerFound] = useState<boolean>(false);\n\n const current = quizzes[currentIndex];\n const hasSelection = typeof selectedIndex === \"number\";\n const selectedIsCorrect =\n hasSelection && selectedIndex !== null\n ? !!current.options[selectedIndex].correct\n : false;\n\n const displayConfetti = (\n options?: Parameters<typeof generateConfettis>[0]\n ) => {\n setConfettiPieces(generateConfettis(options || {}));\n setShowConfetti(true);\n };\n\n const handleSelect = (optionIndex: number) => {\n // If correct answer was already found, prevent further selections\n if (correctAnswerFound) return;\n\n setSelectedIndex(optionIndex);\n if (current.options[optionIndex]?.correct) {\n setCorrectAnswerFound(true);\n setNumCorrect((n) => n + 1);\n displayConfetti();\n }\n };\n\n const handleAnother = () => {\n if (currentIndex + 1 < quizzes.length) {\n setCurrentIndex((idx) => idx + 1);\n setSelectedIndex(null);\n setCorrectAnswerFound(false);\n setShowConfetti(false);\n } else {\n setFinished(true);\n // Longer, denser confetti at the end\n displayConfetti({\n numPieces: 48,\n baseDurationMs: 2400,\n extraRandomMs: 2400,\n });\n }\n };\n\n const handleFinish = () => {\n setFinished(true);\n // Longer, denser confetti at the end\n displayConfetti({\n numPieces: 48,\n baseDurationMs: 2400,\n extraRandomMs: 2400,\n });\n };\n\n const handleRestart = () => {\n setCurrentIndex(0);\n setSelectedIndex(null);\n setFinished(false);\n setShowConfetti(false);\n setCorrectAnswerFound(false);\n setNumCorrect(0);\n };\n\n useEffect(() => {\n if (!showConfetti) return;\n const timeoutMs = finished ? 5200 : 2200;\n const t = setTimeout(() => setShowConfetti(false), timeoutMs);\n return () => clearTimeout(t);\n }, [showConfetti, finished]);\n\n if (finished) {\n const percentage = Math.round((numCorrect / quizzes.length) * 100);\n\n return (\n <div className=\"relative min-h-[400px] flex flex-col items-center justify-center space-y-6 py-8\">\n <ConfettiOverlay pieces={confettiPieces} />\n <div className=\"flex flex-col items-center gap-4 text-center\">\n <div className=\"flex items-center justify-center size-16 rounded-full bg-green-100\">\n <CheckCircle2 className=\"size-10 text-green-600\" />\n </div>\n <h3 className=\"text-2xl font-bold\">{getMessage(percentage)}</h3>\n <div className=\"space-y-2\">\n <div className=\"flex items-baseline justify-center gap-2\">\n <span className=\"text-5xl font-bold text-primary\">\n {numCorrect}\n </span>\n <span className=\"text-xl text-muted-foreground\">\n / {quizzes.length}\n </span>\n </div>\n <div className=\"flex items-center gap-2 justify-center\">\n <Pill className=\"text-sm font-semibold\">\n {percentage}% Correct\n </Pill>\n </div>\n </div>\n <p className=\"text-sm text-muted-foreground max-w-md\">\n Thanks for taking the quiz! You got{\" \"}\n <span className=\"font-semibold text-foreground\">{numCorrect}</span>{\" \"}\n {numCorrect === 1 ? \"question\" : \"questions\"} right out of{\" \"}\n {quizzes.length}.\n </p>\n </div>\n <div className=\"flex gap-3 pt-4\">\n <Button size=\"sm\" onClick={handleRestart}>\n Start over\n </Button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-[300px] space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-xs text-muted-foreground\">\n <Pill>\n Question {currentIndex + 1} of {quizzes.length}\n </Pill>\n <span className=\"hidden sm:inline\">•</span>\n <Pill>Score: {numCorrect}</Pill>\n </span>\n </div>\n <h4 className=\"font-medium\">{current.question}</h4>\n <div className=\"grid grid-cols-1 gap-3\">\n {current.options.map((opt, idx) => {\n const isSelected = selectedIndex === idx;\n const isCorrect = !!opt.correct;\n const showCorrect = hasSelection && isSelected && isCorrect;\n const showWrong = hasSelection && isSelected && !isCorrect;\n\n return (\n <QuizOptionButton\n key={idx}\n state={showCorrect ? \"correct\" : showWrong ? \"wrong\" : \"default\"}\n serial={String.fromCharCode(65 + idx)}\n disabled={correctAnswerFound}\n onClick={() => handleSelect(idx)}\n selected={isSelected}\n >\n {opt.text}\n </QuizOptionButton>\n );\n })}\n </div>\n\n <div className=\"space-y-3\">\n {hasSelection && (\n <div\n className={cn(\n \"flex items-center gap-2 rounded-md px-3 py-2\",\n selectedIsCorrect\n ? \"bg-green-50 text-green-800\"\n : \"bg-red-50 text-red-800\"\n )}\n >\n {selectedIsCorrect ? (\n <CheckCircle2 className=\"size-4\" />\n ) : (\n <XCircle className=\"size-4\" />\n )}\n <p className=\"text-sm\">\n {selectedIsCorrect\n ? \"Correct!\"\n : \"Not quite. Try again or skip to next question!\"}\n </p>\n </div>\n )}\n <div className=\"flex gap-3\">\n <Button size=\"sm\" onClick={handleAnother}>\n {hasSelection && !correctAnswerFound ? \"Skip\" : \"Next Question\"}\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={handleFinish}>\n Finish\n </Button>\n </div>\n </div>\n </div>\n );\n}\n",
1443
+ "demos": [
1444
+ {
1445
+ "name": "default",
1446
+ "code": "\"use client\";\n\nimport { QuizBox, QuizQuestion } from \"@rlx-components/quiz-box\";\n\nexport const Preview = () => {\n const quizzes: Array<QuizQuestion> = [\n {\n question: \"Which of the following is a JavaScript framework?\",\n options: [\n { text: \"React\", correct: true },\n { text: \"Python\" },\n { text: \"Java\" },\n { text: \"C++\" },\n ],\n },\n {\n question: \"What is the capital of France?\",\n options: [\n { text: \"London\" },\n { text: \"Berlin\" },\n { text: \"Paris\", correct: true },\n { text: \"Madrid\" },\n ],\n },\n {\n question: \"Which HTML tag is used for the largest heading?\",\n options: [\n { text: \"<h6>\" },\n { text: \"<h1>\", correct: true },\n { text: \"<heading>\" },\n { text: \"<head>\" },\n ],\n },\n ];\n\n return (\n <div className=\"w-full max-w-2xl\">\n <QuizBox quizzes={quizzes} />\n </div>\n );\n};\n\n"
1447
+ }
1448
+ ],
1449
+ "sourceFiles": {
1450
+ "components/ConfettiOverlay/ConfettiOverlay.tsx": "\"use client\";\n\nimport { cn } from \"@rlx-widgets/base\";\nimport type { ConfettiOverlayProps } from \"./types\";\n\nconst ConfettiOverlay = ({ pieces, className }: ConfettiOverlayProps) => {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"pointer-events-none absolute inset-0 overflow-hidden\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t>\n\t\t\t{pieces.map((cfg, i) => (\n\t\t\t\t<span\n\t\t\t\t\tkey={i}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tleft: `${cfg.left}%`,\n\t\t\t\t\t\ttop: `${cfg.top}%`,\n\t\t\t\t\t\tanimationDelay: `${cfg.delayMs}ms`,\n\t\t\t\t\t\tanimationDuration: `${cfg.durationMs}ms`,\n\t\t\t\t\t\ttransform: `rotate(${cfg.rotate}deg)`,\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"absolute top-0 inline-block\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\t\t\t\twidth: cfg.size,\n\t\t\t\t\t\t\theight: cfg.size * 2,\n\t\t\t\t\t\t\tbackgroundColor: cfg.color,\n\t\t\t\t\t\t\tborderRadius: 2,\n\t\t\t\t\t\t\tanimationName: \"confettiFall\",\n\t\t\t\t\t\t\tanimationTimingFunction:\n\t\t\t\t\t\t\t\t\"cubic-bezier(0.2, 0.6, 0.2, 1)\",\n\t\t\t\t\t\t\tanimationIterationCount: 1,\n\t\t\t\t\t\t\tanimationFillMode: \"forwards\",\n\t\t\t\t\t\t\tanimationDelay: `${cfg.delayMs}ms`,\n\t\t\t\t\t\t\tanimationDuration: `${cfg.durationMs}ms`,\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</span>\n\t\t\t))}\n\t\t\t<style\n\t\t\t\tdangerouslySetInnerHTML={{\n\t\t\t\t\t__html: `\n\t\t\t\t@keyframes confettiFall {\n\t\t\t\t\t0% {\n\t\t\t\t\t\topacity: 0;\n\t\t\t\t\t\ttransform: translateY(-10px) rotate(0deg);\n\t\t\t\t\t}\n\t\t\t\t\t10% {\n\t\t\t\t\t\topacity: 1;\n\t\t\t\t\t}\n\t\t\t\t\t100% {\n\t\t\t\t\t\topacity: 0;\n\t\t\t\t\t\ttransform: translateY(110%) rotate(720deg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t`,\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>\n\t);\n};\n\nexport { ConfettiOverlay };\n",
1451
+ "components/ConfettiOverlay/types/ConfettiOverlayProps.t.ts": "import { ConfettiPiece } from \"../../../types\";\n\nexport type ConfettiOverlayProps = {\n pieces: Array<ConfettiPiece>;\n className?: string;\n};\n\n",
1452
+ "components/Pill.tsx": "import { cn } from \"@rlx-widgets/base\";\nimport { ComponentProps } from \"react\";\n\nexport const Pill = ({\n\tchildren,\n\tclassName,\n\t...props\n}: ComponentProps<\"span\">) => {\n\treturn (\n\t\t<span\n\t\t\tclassName={cn(\n\t\t\t\t\"inline-flex items-center rounded-full bg-secondary px-2 py-1\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t</span>\n\t);\n};\n",
1453
+ "components/QuizOptionButton/QuizOptionButton.tsx": "\"use client\";\n\nimport { cn } from \"@rlx-widgets/base\";\nimport { ConfettiOverlay } from \"../ConfettiOverlay\";\nimport { cva, VariantProps } from \"class-variance-authority\";\nimport { generateConfettis } from \"../../utils\";\nimport { ComponentProps, useMemo } from \"react\";\n\nconst quizOptionButtonVariants = cva(\n \"relative rounded-lg border p-3 text-left transition-all hover:shadow-md active:scale-[0.99] cursor-pointer\",\n {\n variants: {\n state: {\n default: \"bg-muted/40 hover:bg-muted/60\",\n correct: \"bg-green-100 border-green-300 text-green-900\",\n wrong: \"bg-red-50 border-red-300 text-red-900\",\n },\n },\n defaultVariants: {\n state: \"default\",\n },\n }\n);\n\nexport type QuizOptionButtonProps = ComponentProps<\"button\"> &\n VariantProps<typeof quizOptionButtonVariants> & {\n selected?: boolean;\n serial: number | string;\n };\n\nexport const QuizOptionButton = ({\n className,\n state,\n selected = false,\n serial,\n children,\n ...props\n}: QuizOptionButtonProps) => {\n const confettiPieces = useMemo(() => {\n if (selected && state === \"correct\") {\n return generateConfettis();\n }\n return [];\n }, [selected, state]);\n\n return (\n <button\n data-slot=\"button\"\n className={cn(quizOptionButtonVariants({ state, className }))}\n type=\"button\"\n {...props}\n >\n {selected && state === \"correct\" && (\n <ConfettiOverlay pieces={confettiPieces} />\n )}\n <span className=\"flex items-center gap-3\">\n <span className=\"inline-flex size-6 items-center justify-center rounded-full text-xs font-semibold\">\n {serial}\n </span>\n <span className=\"text-sm font-medium\">{children}</span>\n </span>\n </button>\n );\n};\n",
1454
+ "types/ConfettiPiece.t.ts": "export type ConfettiPiece = {\n\tleft: number;\n\ttop: number;\n\tdelayMs: number;\n\tdurationMs: number;\n\trotate: number;\n\tsize: number;\n\tcolor: string;\n};\n",
1455
+ "types/QuizBoxProps.t.ts": "import { QuizQuestion } from \"./QuizQuestion.t\";\n\nexport type QuizBoxProps = {\n quizzes: Array<QuizQuestion>;\n};\n\n",
1456
+ "types/QuizQuestion.t.ts": "export type QuizQuestion = {\n\tquestion: string;\n\toptions: Array<{\n\t\ttext: string;\n\t\tcorrect?: boolean;\n\t}>;\n};\n",
1457
+ "utils/generateConfettis.ts": "export const generateConfettis = (options?: {\n\tnumPieces?: number;\n\tbaseDurationMs?: number;\n\textraRandomMs?: number;\n}) => {\n\tconst {\n\t\tnumPieces = 24,\n\t\tbaseDurationMs = 2000,\n\t\textraRandomMs = 900,\n\t} = options || {};\n\n\tconst colors = [\"#22c55e\", \"#3b82f6\", \"#eab308\", \"#ef4444\", \"#a855f7\"];\n\tconst pieces = Array.from({ length: numPieces }).map((_, i) => ({\n\t\tleft: Math.random() * 100,\n\t\ttop: Math.random() * 100,\n\t\tdelayMs: 0,\n\t\tdurationMs: baseDurationMs + Math.floor(Math.random() * extraRandomMs),\n\t\trotate: Math.floor(Math.random() * 360),\n\t\tsize: 8 + Math.floor(Math.random() * 8),\n\t\tcolor: colors[i % colors.length],\n\t}));\n\n\treturn pieces;\n};\n",
1458
+ "utils/getMessage.ts": "export const getMessage = (percentage: number): string => {\n if (percentage === 100) return \"Perfect Score! 🎉\";\n if (percentage >= 80) return \"Excellent Work! 🚀\";\n if (percentage >= 60) return \"Great Job! 👍\";\n return \"Nice Try! 💪\";\n};\n\n"
1459
+ }
1460
+ },
1435
1461
  {
1436
1462
  "name": "Selectable Button",
1437
1463
  "exportName": "SelectableButton",
@@ -1648,5 +1674,5 @@
1648
1674
  "sourceCode": "export const isValidDate = (date: Date) => {\n return !Number.isNaN(date.getTime());\n};\n\nexport default isValidDate;\n"
1649
1675
  }
1650
1676
  ],
1651
- "extractedAt": "2025-12-08T03:17:41.438Z"
1677
+ "extractedAt": "2025-12-08T04:24:14.975Z"
1652
1678
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlx-ui/mcp",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "MCP (Model Context Protocol) Server for RLX UI components",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlx-ui/mcp",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "MCP (Model Context Protocol) Server for RLX UI components",
5
5
  "publishConfig": {
6
6
  "access": "public"