react-matchings 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.md +657 -0
- package/dist/index.css +1 -1
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fares Galal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1 +1,658 @@
|
|
|
1
|
+
# react-matchings
|
|
1
2
|
|
|
3
|
+
A React component for interactive question-answer matching. Create engaging quiz and assessment interfaces where users can drag and drop connections between questions and answers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Drag and Drop Interface** - Intuitive drag-and-drop matching experience
|
|
8
|
+
- 🎨 **Highly Customizable** - Customize colors, styling, and behavior through props
|
|
9
|
+
- ♿ **Accessible** - Built with accessibility in mind
|
|
10
|
+
- 🎭 **Dynamic Styling** - Apply conditional styles based on component state
|
|
11
|
+
- 📱 **Responsive** - Works across different screen sizes
|
|
12
|
+
- 🎪 **Interactive Feedback** - Visual feedback during dragging and matching
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install react-matchings
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
yarn add react-matchings
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add react-matchings
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Basic Usage
|
|
29
|
+
|
|
30
|
+
First, import the component and its CSS:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { Matching } from "react-matchings";
|
|
34
|
+
import "react-matchings/dist/index.css";
|
|
35
|
+
|
|
36
|
+
function App() {
|
|
37
|
+
const questions = [
|
|
38
|
+
{ id: 1, text: "What is React?" },
|
|
39
|
+
{ id: 2, text: "What is TypeScript?" },
|
|
40
|
+
{ id: 3, text: "What is JavaScript?" },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const answers = [
|
|
44
|
+
{ id: 1, text: "A JavaScript library for building UIs" },
|
|
45
|
+
{ id: 2, text: "A typed superset of JavaScript" },
|
|
46
|
+
{ id: 3, text: "A programming language" },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const handleMatchChange = (matches) => {
|
|
50
|
+
console.log("Current matches:", matches);
|
|
51
|
+
// matches format: [{ questionId: 1, answerId: 1 }, ...]
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Matching
|
|
56
|
+
questions={questions}
|
|
57
|
+
answers={answers}
|
|
58
|
+
onChange={handleMatchChange}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Props
|
|
65
|
+
|
|
66
|
+
### Required Props
|
|
67
|
+
|
|
68
|
+
#### `questions`
|
|
69
|
+
|
|
70
|
+
Array of question objects to display on the left side.
|
|
71
|
+
|
|
72
|
+
**Type:** `{ id: number; text: string }[]`
|
|
73
|
+
|
|
74
|
+
**Example:**
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const questions = [
|
|
78
|
+
{ id: 1, text: "Capital of France?" },
|
|
79
|
+
{ id: 2, text: "Capital of Japan?" },
|
|
80
|
+
{ id: 3, text: "Capital of Australia?" },
|
|
81
|
+
];
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
#### `answers`
|
|
87
|
+
|
|
88
|
+
Array of answer objects to display on the right side.
|
|
89
|
+
|
|
90
|
+
**Type:** `{ id: number; text: string }[]`
|
|
91
|
+
|
|
92
|
+
**Example:**
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const answers = [
|
|
96
|
+
{ id: 1, text: "Paris" },
|
|
97
|
+
{ id: 2, text: "Tokyo" },
|
|
98
|
+
{ id: 3, text: "Canberra" },
|
|
99
|
+
];
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Note:** The number of questions and answers don't need to match. Users can connect any question to any answer.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
#### `onChange`
|
|
107
|
+
|
|
108
|
+
Callback function that is called whenever the matches change.
|
|
109
|
+
|
|
110
|
+
**Type:** `(matches: { questionId: number | string; answerId: number | string }[]) => void`
|
|
111
|
+
|
|
112
|
+
**Example:**
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
const handleChange = (matches) => {
|
|
116
|
+
// matches is an array of connections
|
|
117
|
+
// Example: [{ questionId: 1, answerId: 2 }, { questionId: 2, answerId: 1 }]
|
|
118
|
+
console.log("Matches updated:", matches);
|
|
119
|
+
// Save to state, send to API, etc.
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
<Matching onChange={handleChange} ... />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### Optional Props
|
|
128
|
+
|
|
129
|
+
#### `className`
|
|
130
|
+
|
|
131
|
+
Additional CSS classes to apply to the container element.
|
|
132
|
+
|
|
133
|
+
**Type:** `string`
|
|
134
|
+
|
|
135
|
+
**Default:** `undefined`
|
|
136
|
+
|
|
137
|
+
**Example:**
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
<Matching
|
|
141
|
+
className="my-8 p-6 border border-gray-300 rounded-lg"
|
|
142
|
+
questions={questions}
|
|
143
|
+
answers={answers}
|
|
144
|
+
onChange={handleChange}
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
#### `questionClassName`
|
|
151
|
+
|
|
152
|
+
Custom CSS classes for question buttons. Can be a string or a function that receives state.
|
|
153
|
+
|
|
154
|
+
**Type:** `string | ((state: { isMatched: boolean; isDragging: boolean }) => string)`
|
|
155
|
+
|
|
156
|
+
**Default:** `undefined`
|
|
157
|
+
|
|
158
|
+
**Example (string):**
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<Matching
|
|
162
|
+
questionClassName="bg-blue-500 hover:bg-blue-600 text-white font-bold"
|
|
163
|
+
questions={questions}
|
|
164
|
+
answers={answers}
|
|
165
|
+
onChange={handleChange}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Example (function with state):**
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<Matching
|
|
173
|
+
questionClassName={({ isMatched, isDragging }) => {
|
|
174
|
+
if (isDragging) return "bg-yellow-500 text-black";
|
|
175
|
+
if (isMatched) return "bg-green-500 text-white";
|
|
176
|
+
return "bg-gray-200 text-gray-800";
|
|
177
|
+
}}
|
|
178
|
+
questions={questions}
|
|
179
|
+
answers={answers}
|
|
180
|
+
onChange={handleChange}
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**State properties:**
|
|
185
|
+
|
|
186
|
+
- `isMatched`: `boolean` - Whether the question is currently matched to an answer
|
|
187
|
+
- `isDragging`: `boolean` - Whether the question is currently being dragged
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
#### `answerClassName`
|
|
192
|
+
|
|
193
|
+
Custom CSS classes for answer buttons. Can be a string or a function that receives state.
|
|
194
|
+
|
|
195
|
+
**Type:** `string | ((state: { isMatched: boolean; isHovering: boolean }) => string)`
|
|
196
|
+
|
|
197
|
+
**Default:** `undefined`
|
|
198
|
+
|
|
199
|
+
**Example (string):**
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<Matching
|
|
203
|
+
answerClassName="bg-purple-500 hover:bg-purple-600 text-white"
|
|
204
|
+
questions={questions}
|
|
205
|
+
answers={answers}
|
|
206
|
+
onChange={handleChange}
|
|
207
|
+
/>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Example (function with state):**
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<Matching
|
|
214
|
+
answerClassName={({ isMatched, isHovering }) => {
|
|
215
|
+
if (isHovering && !isMatched)
|
|
216
|
+
return "bg-yellow-300 border-2 border-yellow-500";
|
|
217
|
+
if (isMatched) return "bg-green-400 text-white font-semibold";
|
|
218
|
+
return "bg-gray-100 text-gray-700";
|
|
219
|
+
}}
|
|
220
|
+
questions={questions}
|
|
221
|
+
answers={answers}
|
|
222
|
+
onChange={handleChange}
|
|
223
|
+
/>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**State properties:**
|
|
227
|
+
|
|
228
|
+
- `isMatched`: `boolean` - Whether the answer is currently matched to a question
|
|
229
|
+
- `isHovering`: `boolean` - Whether a question is being dragged over this answer
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
#### `lineClassName`
|
|
234
|
+
|
|
235
|
+
Custom CSS classes for the SVG container that draws the connecting lines.
|
|
236
|
+
|
|
237
|
+
**Type:** `string`
|
|
238
|
+
|
|
239
|
+
**Default:** `undefined`
|
|
240
|
+
|
|
241
|
+
**Example:**
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
<Matching
|
|
245
|
+
lineClassName="opacity-90"
|
|
246
|
+
questions={questions}
|
|
247
|
+
answers={answers}
|
|
248
|
+
onChange={handleChange}
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
#### `lineColor`
|
|
255
|
+
|
|
256
|
+
Color of the connecting lines.
|
|
257
|
+
|
|
258
|
+
**Type:** `string`
|
|
259
|
+
|
|
260
|
+
**Default:** `"black"`
|
|
261
|
+
|
|
262
|
+
**Example:**
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
<Matching
|
|
266
|
+
lineColor="#3b82f6" // blue
|
|
267
|
+
questions={questions}
|
|
268
|
+
answers={answers}
|
|
269
|
+
onChange={handleChange}
|
|
270
|
+
/>
|
|
271
|
+
|
|
272
|
+
// Or use CSS color names
|
|
273
|
+
<Matching
|
|
274
|
+
lineColor="rgb(59, 130, 246)"
|
|
275
|
+
questions={questions}
|
|
276
|
+
answers={answers}
|
|
277
|
+
onChange={handleChange}
|
|
278
|
+
/>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
#### `circleColor`
|
|
284
|
+
|
|
285
|
+
Color of the circles at the ends of the connecting lines.
|
|
286
|
+
|
|
287
|
+
**Type:** `string`
|
|
288
|
+
|
|
289
|
+
**Default:** `"white"`
|
|
290
|
+
|
|
291
|
+
**Example:**
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
<Matching
|
|
295
|
+
circleColor="#f3f4f6" // light gray
|
|
296
|
+
questions={questions}
|
|
297
|
+
answers={answers}
|
|
298
|
+
onChange={handleChange}
|
|
299
|
+
/>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
#### `circleRadius`
|
|
305
|
+
|
|
306
|
+
Radius of the circles at the ends of the connecting lines, in pixels.
|
|
307
|
+
|
|
308
|
+
**Type:** `number`
|
|
309
|
+
|
|
310
|
+
**Default:** `8`
|
|
311
|
+
|
|
312
|
+
**Example:**
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
<Matching
|
|
316
|
+
circleRadius={12}
|
|
317
|
+
questions={questions}
|
|
318
|
+
answers={answers}
|
|
319
|
+
onChange={handleChange}
|
|
320
|
+
/>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
#### `offset`
|
|
326
|
+
|
|
327
|
+
Offset distance from the edges where lines connect, in pixels. Controls how far from the edge the connection points are.
|
|
328
|
+
|
|
329
|
+
**Type:** `number`
|
|
330
|
+
|
|
331
|
+
**Default:** `10`
|
|
332
|
+
|
|
333
|
+
**Example:**
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
<Matching
|
|
337
|
+
offset={15}
|
|
338
|
+
questions={questions}
|
|
339
|
+
answers={answers}
|
|
340
|
+
onChange={handleChange}
|
|
341
|
+
/>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
#### `disabled`
|
|
347
|
+
|
|
348
|
+
Whether the component is disabled. When disabled, users cannot create or remove matches.
|
|
349
|
+
|
|
350
|
+
**Type:** `boolean`
|
|
351
|
+
|
|
352
|
+
**Default:** `false`
|
|
353
|
+
|
|
354
|
+
**Example:**
|
|
355
|
+
|
|
356
|
+
```tsx
|
|
357
|
+
const [isDisabled, setIsDisabled] = useState(false);
|
|
358
|
+
|
|
359
|
+
<Matching
|
|
360
|
+
disabled={isDisabled}
|
|
361
|
+
questions={questions}
|
|
362
|
+
answers={answers}
|
|
363
|
+
onChange={handleChange}
|
|
364
|
+
/>
|
|
365
|
+
|
|
366
|
+
<button onClick={() => setIsDisabled(!isDisabled)}>
|
|
367
|
+
{isDisabled ? "Enable" : "Disable"} Matching
|
|
368
|
+
</button>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Complete Examples
|
|
374
|
+
|
|
375
|
+
### Example 1: Basic Quiz Component
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
import { useState } from "react";
|
|
379
|
+
import { Matching } from "react-matchings";
|
|
380
|
+
import "react-matchings/dist/index.css";
|
|
381
|
+
|
|
382
|
+
function QuizApp() {
|
|
383
|
+
const questions = [
|
|
384
|
+
{ id: 1, text: "What is the capital of France?" },
|
|
385
|
+
{ id: 2, text: "What is the capital of Japan?" },
|
|
386
|
+
{ id: 3, text: "What is the capital of Australia?" },
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const answers = [
|
|
390
|
+
{ id: 1, text: "Paris" },
|
|
391
|
+
{ id: 2, text: "Tokyo" },
|
|
392
|
+
{ id: 3, text: "Canberra" },
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
const [matches, setMatches] = useState([]);
|
|
396
|
+
const [submitted, setSubmitted] = useState(false);
|
|
397
|
+
|
|
398
|
+
const handleSubmit = () => {
|
|
399
|
+
setSubmitted(true);
|
|
400
|
+
// Check answers, calculate score, etc.
|
|
401
|
+
console.log("Submitted matches:", matches);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<div className="p-8 max-w-4xl mx-auto">
|
|
406
|
+
<h1 className="text-2xl font-bold mb-6">Geography Quiz</h1>
|
|
407
|
+
|
|
408
|
+
<Matching
|
|
409
|
+
questions={questions}
|
|
410
|
+
answers={answers}
|
|
411
|
+
onChange={setMatches}
|
|
412
|
+
disabled={submitted}
|
|
413
|
+
/>
|
|
414
|
+
|
|
415
|
+
<button
|
|
416
|
+
onClick={handleSubmit}
|
|
417
|
+
disabled={submitted || matches.length === 0}
|
|
418
|
+
className="mt-6 px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-gray-400"
|
|
419
|
+
>
|
|
420
|
+
Submit Answers
|
|
421
|
+
</button>
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Example 2: Custom Styled Component
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
import { Matching } from "react-matchings";
|
|
431
|
+
import "react-matchings/dist/index.css";
|
|
432
|
+
|
|
433
|
+
function StyledMatching() {
|
|
434
|
+
const questions = [
|
|
435
|
+
{ id: 1, text: "Question 1" },
|
|
436
|
+
{ id: 2, text: "Question 2" },
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
const answers = [
|
|
440
|
+
{ id: 1, text: "Answer 1" },
|
|
441
|
+
{ id: 2, text: "Answer 2" },
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<Matching
|
|
446
|
+
questions={questions}
|
|
447
|
+
answers={answers}
|
|
448
|
+
onChange={(matches) => console.log(matches)}
|
|
449
|
+
className="p-8 bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl shadow-lg"
|
|
450
|
+
questionClassName={({ isMatched, isDragging }) => {
|
|
451
|
+
let base = "p-4 rounded-lg font-semibold transition-all duration-200 ";
|
|
452
|
+
if (isDragging)
|
|
453
|
+
base += "bg-yellow-400 text-yellow-900 shadow-lg scale-105";
|
|
454
|
+
else if (isMatched) base += "bg-green-500 text-white";
|
|
455
|
+
else base += "bg-blue-500 text-white hover:bg-blue-600";
|
|
456
|
+
return base;
|
|
457
|
+
}}
|
|
458
|
+
answerClassName={({ isMatched, isHovering }) => {
|
|
459
|
+
let base = "p-4 rounded-lg font-semibold transition-all duration-200 ";
|
|
460
|
+
if (isHovering && !isMatched)
|
|
461
|
+
base += "bg-yellow-300 text-yellow-900 border-2 border-yellow-500";
|
|
462
|
+
else if (isMatched) base += "bg-green-500 text-white";
|
|
463
|
+
else base += "bg-purple-500 text-white hover:bg-purple-600";
|
|
464
|
+
return base;
|
|
465
|
+
}}
|
|
466
|
+
lineColor="#8b5cf6"
|
|
467
|
+
circleColor="#e9d5ff"
|
|
468
|
+
circleRadius={10}
|
|
469
|
+
offset={12}
|
|
470
|
+
/>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Example 3: With Initial Matches
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
import { useState, useEffect } from "react";
|
|
479
|
+
import { Matching } from "react-matchings";
|
|
480
|
+
import "react-matchings/dist/index.css";
|
|
481
|
+
|
|
482
|
+
function MatchingWithInitialState() {
|
|
483
|
+
const questions = [
|
|
484
|
+
{ id: 1, text: "React" },
|
|
485
|
+
{ id: 2, text: "Vue" },
|
|
486
|
+
{ id: 3, text: "Angular" },
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
const answers = [
|
|
490
|
+
{ id: 1, text: "Facebook" },
|
|
491
|
+
{ id: 2, text: "Evan You" },
|
|
492
|
+
{ id: 3, text: "Google" },
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
const [matches, setMatches] = useState([]);
|
|
496
|
+
|
|
497
|
+
// Load initial matches (e.g., from localStorage or API)
|
|
498
|
+
useEffect(() => {
|
|
499
|
+
const savedMatches = localStorage.getItem("matches");
|
|
500
|
+
if (savedMatches) {
|
|
501
|
+
setMatches(JSON.parse(savedMatches));
|
|
502
|
+
}
|
|
503
|
+
}, []);
|
|
504
|
+
|
|
505
|
+
// Save matches when they change
|
|
506
|
+
const handleMatchChange = (newMatches) => {
|
|
507
|
+
setMatches(newMatches);
|
|
508
|
+
localStorage.setItem("matches", JSON.stringify(newMatches));
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
return (
|
|
512
|
+
<Matching
|
|
513
|
+
questions={questions}
|
|
514
|
+
answers={answers}
|
|
515
|
+
onChange={handleMatchChange}
|
|
516
|
+
/>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Example 4: Assessment with Validation
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
import { useState, useMemo } from "react";
|
|
525
|
+
import { Matching } from "react-matchings";
|
|
526
|
+
import "react-matchings/dist/index.css";
|
|
527
|
+
|
|
528
|
+
function AssessmentComponent() {
|
|
529
|
+
const questions = [
|
|
530
|
+
{ id: 1, text: "Primary color" },
|
|
531
|
+
{ id: 2, text: "Secondary color" },
|
|
532
|
+
{ id: 3, text: "Tertiary color" },
|
|
533
|
+
];
|
|
534
|
+
|
|
535
|
+
const answers = [
|
|
536
|
+
{ id: 1, text: "Red, Blue, Yellow" },
|
|
537
|
+
{ id: 2, text: "Orange, Green, Purple" },
|
|
538
|
+
{ id: 3, text: "Red-Orange, Yellow-Green, Blue-Purple" },
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
// Correct answers
|
|
542
|
+
const correctMatches = [
|
|
543
|
+
{ questionId: 1, answerId: 1 },
|
|
544
|
+
{ questionId: 2, answerId: 2 },
|
|
545
|
+
{ questionId: 3, answerId: 3 },
|
|
546
|
+
];
|
|
547
|
+
|
|
548
|
+
const [matches, setMatches] = useState([]);
|
|
549
|
+
const [showResults, setShowResults] = useState(false);
|
|
550
|
+
|
|
551
|
+
const score = useMemo(() => {
|
|
552
|
+
if (!showResults) return null;
|
|
553
|
+
const correct = matches.filter((match) =>
|
|
554
|
+
correctMatches.some(
|
|
555
|
+
(cm) =>
|
|
556
|
+
cm.questionId === match.questionId && cm.answerId === match.answerId
|
|
557
|
+
)
|
|
558
|
+
).length;
|
|
559
|
+
return { correct, total: questions.length };
|
|
560
|
+
}, [matches, showResults]);
|
|
561
|
+
|
|
562
|
+
return (
|
|
563
|
+
<div className="p-8">
|
|
564
|
+
<Matching
|
|
565
|
+
questions={questions}
|
|
566
|
+
answers={answers}
|
|
567
|
+
onChange={setMatches}
|
|
568
|
+
disabled={showResults}
|
|
569
|
+
/>
|
|
570
|
+
|
|
571
|
+
{showResults && score && (
|
|
572
|
+
<div className="mt-6 p-4 bg-gray-100 rounded">
|
|
573
|
+
<p className="text-lg font-semibold">
|
|
574
|
+
Score: {score.correct} / {score.total}
|
|
575
|
+
</p>
|
|
576
|
+
</div>
|
|
577
|
+
)}
|
|
578
|
+
|
|
579
|
+
<button
|
|
580
|
+
onClick={() => setShowResults(true)}
|
|
581
|
+
disabled={showResults || matches.length < questions.length}
|
|
582
|
+
className="mt-4 px-6 py-2 bg-blue-500 text-white rounded"
|
|
583
|
+
>
|
|
584
|
+
Check Answers
|
|
585
|
+
</button>
|
|
586
|
+
</div>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## How It Works
|
|
592
|
+
|
|
593
|
+
1. **Drag to Connect**: Click and hold on a question, then drag to an answer and release to create a connection.
|
|
594
|
+
|
|
595
|
+
2. **Remove Connection**: Click on a matched question to remove its connection.
|
|
596
|
+
|
|
597
|
+
3. **Visual Feedback**:
|
|
598
|
+
|
|
599
|
+
- Dragging shows a dashed line following your cursor
|
|
600
|
+
- Matched items are visually distinguished
|
|
601
|
+
- Hovering over answers while dragging highlights them
|
|
602
|
+
|
|
603
|
+
4. **State Management**: The `onChange` callback receives an array of all current matches whenever they change.
|
|
604
|
+
|
|
605
|
+
## Styling
|
|
606
|
+
|
|
607
|
+
The component uses Tailwind CSS for styling. Make sure to import the CSS file:
|
|
608
|
+
|
|
609
|
+
```tsx
|
|
610
|
+
import "react-matchings/dist/index.css";
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
You can override default styles using the className props, or customize the colors using the color props.
|
|
614
|
+
|
|
615
|
+
## TypeScript Support
|
|
616
|
+
|
|
617
|
+
This package includes TypeScript definitions. The component is fully typed:
|
|
618
|
+
|
|
619
|
+
```tsx
|
|
620
|
+
import { Matching } from "react-matchings";
|
|
621
|
+
|
|
622
|
+
// Types are automatically inferred
|
|
623
|
+
const handleChange = (
|
|
624
|
+
matches: { questionId: number | string; answerId: number | string }[]
|
|
625
|
+
) => {
|
|
626
|
+
// matches is properly typed
|
|
627
|
+
};
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
## Accessibility
|
|
631
|
+
|
|
632
|
+
The component includes accessibility features:
|
|
633
|
+
|
|
634
|
+
- Proper ARIA attributes (`aria-pressed`)
|
|
635
|
+
- Keyboard-friendly interactions
|
|
636
|
+
- Focus management
|
|
637
|
+
- Semantic HTML structure
|
|
638
|
+
|
|
639
|
+
## Browser Support
|
|
640
|
+
|
|
641
|
+
Works in all modern browsers that support:
|
|
642
|
+
|
|
643
|
+
- React 18+
|
|
644
|
+
- CSS Grid
|
|
645
|
+
- SVG
|
|
646
|
+
- Drag and Drop API
|
|
647
|
+
|
|
648
|
+
## License
|
|
649
|
+
|
|
650
|
+
MIT
|
|
651
|
+
|
|
652
|
+
## Contributing
|
|
653
|
+
|
|
654
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
655
|
+
|
|
656
|
+
## Author
|
|
657
|
+
|
|
658
|
+
Fares Galal
|
package/dist/index.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-font-weight:initial;--tw-duration:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--font-weight-medium:500;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.z-10{z-index:10}.z-20{z-index:20}.grid{display:grid}.h-full{height:100%}.w-full{width:100%}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.gap-10{gap:calc(var(--spacing)*10)}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}.rounded{border-radius:.25rem}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600{border-color:var(--color-gray-600)}.bg-black{background-color:var(--color-black)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-none{background-image:none}.p-4{padding:calc(var(--spacing)*4)}.ps-7{padding-inline-start:calc(var(--spacing)*7)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.text-white{color:var(--color-white)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-blue-50:oklch(97% .014 254.604);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-lg:.5rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.z-10{z-index:10}.z-20{z-index:20}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.my-8{margin-block:calc(var(--spacing)*8)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.grid{display:grid}.h-full{height:100%}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.scale-105{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.gap-10{gap:calc(var(--spacing)*10)}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600{border-color:var(--color-gray-600)}.border-yellow-500{border-color:var(--color-yellow-500)}.bg-black{background-color:var(--color-black)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-purple-500{background-color:var(--color-purple-500)}.bg-yellow-300{background-color:var(--color-yellow-300)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-none{background-image:none}.from-blue-50{--tw-gradient-from:var(--color-blue-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-purple-50{--tw-gradient-to:var(--color-purple-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.ps-7{padding-inline-start:calc(var(--spacing)*7)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-black{color:var(--color-black)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-white{color:var(--color-white)}.text-yellow-900{color:var(--color-yellow-900)}.opacity-90{opacity:.9}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:bg-gray-400:disabled{background-color:var(--color-gray-400)}}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-matchings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "A React component for matching questions and answers",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsup src/index.ts --format cjs,esm --dts && npm run build:css",
|
|
10
10
|
"build:css": "npx tailwindcss -i ./src/index.css -o ./dist/index.css --minify",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
11
12
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
13
|
"lint": "tsc"
|
|
13
14
|
},
|