react-matchings 0.0.4 → 0.0.6
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 +63 -111
- package/dist/index.css +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/matching.tsx +2 -2
package/README.md
CHANGED
|
@@ -27,10 +27,10 @@ pnpm add react-matchings
|
|
|
27
27
|
|
|
28
28
|
## Basic Usage
|
|
29
29
|
|
|
30
|
-
First, import the component and
|
|
30
|
+
First, import the component, its types, and CSS:
|
|
31
31
|
|
|
32
32
|
```tsx
|
|
33
|
-
import { Matching } from "react-matchings";
|
|
33
|
+
import { Matching, type TMatch } from "react-matchings";
|
|
34
34
|
import "react-matchings/dist/index.css";
|
|
35
35
|
|
|
36
36
|
function App() {
|
|
@@ -46,7 +46,7 @@ function App() {
|
|
|
46
46
|
{ id: 3, text: "A programming language" },
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
-
const handleMatchChange = (matches) => {
|
|
49
|
+
const handleMatchChange = (matches: TMatch[]): void => {
|
|
50
50
|
console.log("Current matches:", matches);
|
|
51
51
|
// matches format: [{ questionId: 1, answerId: 1 }, ...]
|
|
52
52
|
};
|
|
@@ -103,16 +103,24 @@ const answers = [
|
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
|
+
### Optional Props
|
|
107
|
+
|
|
106
108
|
#### `onChange`
|
|
107
109
|
|
|
108
110
|
Callback function that is called whenever the matches change.
|
|
109
111
|
|
|
110
|
-
**Type:** `(matches:
|
|
112
|
+
**Type:** `(matches: TMatch[]) => void`
|
|
113
|
+
|
|
114
|
+
**Default:** `undefined`
|
|
115
|
+
|
|
116
|
+
**Note:** Import `TMatch` type from the package: `import { type TMatch } from "react-matchings";`
|
|
111
117
|
|
|
112
118
|
**Example:**
|
|
113
119
|
|
|
114
120
|
```tsx
|
|
115
|
-
|
|
121
|
+
import { Matching, type TMatch } from "react-matchings";
|
|
122
|
+
|
|
123
|
+
const handleChange = (matches: TMatch[]): void => {
|
|
116
124
|
// matches is an array of connections
|
|
117
125
|
// Example: [{ questionId: 1, answerId: 2 }, { questionId: 2, answerId: 1 }]
|
|
118
126
|
console.log("Matches updated:", matches);
|
|
@@ -122,9 +130,9 @@ const handleChange = (matches) => {
|
|
|
122
130
|
<Matching onChange={handleChange} ... />
|
|
123
131
|
```
|
|
124
132
|
|
|
125
|
-
|
|
133
|
+
**Note:** This prop is optional. If not provided, the component will still function but won't notify parent components of changes.
|
|
126
134
|
|
|
127
|
-
|
|
135
|
+
---
|
|
128
136
|
|
|
129
137
|
#### `className`
|
|
130
138
|
|
|
@@ -149,13 +157,13 @@ Additional CSS classes to apply to the container element.
|
|
|
149
157
|
|
|
150
158
|
#### `questionClassName`
|
|
151
159
|
|
|
152
|
-
Custom CSS classes for question buttons.
|
|
160
|
+
Custom CSS classes for question buttons.
|
|
153
161
|
|
|
154
|
-
**Type:** `string
|
|
162
|
+
**Type:** `string`
|
|
155
163
|
|
|
156
164
|
**Default:** `undefined`
|
|
157
165
|
|
|
158
|
-
**Example
|
|
166
|
+
**Example:**
|
|
159
167
|
|
|
160
168
|
```tsx
|
|
161
169
|
<Matching
|
|
@@ -166,73 +174,11 @@ Custom CSS classes for question buttons. Can be a string or a function that rece
|
|
|
166
174
|
/>
|
|
167
175
|
```
|
|
168
176
|
|
|
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
177
|
---
|
|
190
178
|
|
|
191
179
|
#### `answerClassName`
|
|
192
180
|
|
|
193
|
-
Custom CSS classes for answer buttons.
|
|
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.
|
|
181
|
+
Custom CSS classes for answer buttons.
|
|
236
182
|
|
|
237
183
|
**Type:** `string`
|
|
238
184
|
|
|
@@ -242,7 +188,7 @@ Custom CSS classes for the SVG container that draws the connecting lines.
|
|
|
242
188
|
|
|
243
189
|
```tsx
|
|
244
190
|
<Matching
|
|
245
|
-
|
|
191
|
+
answerClassName="bg-purple-500 hover:bg-purple-600 text-white"
|
|
246
192
|
questions={questions}
|
|
247
193
|
answers={answers}
|
|
248
194
|
onChange={handleChange}
|
|
@@ -376,7 +322,7 @@ const [isDisabled, setIsDisabled] = useState(false);
|
|
|
376
322
|
|
|
377
323
|
```tsx
|
|
378
324
|
import { useState } from "react";
|
|
379
|
-
import { Matching } from "react-matchings";
|
|
325
|
+
import { Matching, type TMatch } from "react-matchings";
|
|
380
326
|
import "react-matchings/dist/index.css";
|
|
381
327
|
|
|
382
328
|
function QuizApp() {
|
|
@@ -392,10 +338,10 @@ function QuizApp() {
|
|
|
392
338
|
{ id: 3, text: "Canberra" },
|
|
393
339
|
];
|
|
394
340
|
|
|
395
|
-
const [matches, setMatches] = useState([]);
|
|
396
|
-
const [submitted, setSubmitted] = useState(false);
|
|
341
|
+
const [matches, setMatches] = useState<TMatch[]>([]);
|
|
342
|
+
const [submitted, setSubmitted] = useState<boolean>(false);
|
|
397
343
|
|
|
398
|
-
const handleSubmit = () => {
|
|
344
|
+
const handleSubmit = (): void => {
|
|
399
345
|
setSubmitted(true);
|
|
400
346
|
// Check answers, calculate score, etc.
|
|
401
347
|
console.log("Submitted matches:", matches);
|
|
@@ -427,7 +373,7 @@ function QuizApp() {
|
|
|
427
373
|
### Example 2: Custom Styled Component
|
|
428
374
|
|
|
429
375
|
```tsx
|
|
430
|
-
import { Matching } from "react-matchings";
|
|
376
|
+
import { Matching, type TMatch } from "react-matchings";
|
|
431
377
|
import "react-matchings/dist/index.css";
|
|
432
378
|
|
|
433
379
|
function StyledMatching() {
|
|
@@ -441,28 +387,18 @@ function StyledMatching() {
|
|
|
441
387
|
{ id: 2, text: "Answer 2" },
|
|
442
388
|
];
|
|
443
389
|
|
|
390
|
+
const handleChange = (matches: TMatch[]): void => {
|
|
391
|
+
console.log(matches);
|
|
392
|
+
};
|
|
393
|
+
|
|
444
394
|
return (
|
|
445
395
|
<Matching
|
|
446
396
|
questions={questions}
|
|
447
397
|
answers={answers}
|
|
448
|
-
onChange={
|
|
398
|
+
onChange={handleChange}
|
|
449
399
|
className="p-8 bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl shadow-lg"
|
|
450
|
-
questionClassName=
|
|
451
|
-
|
|
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
|
-
}}
|
|
400
|
+
questionClassName="p-4 rounded-lg font-semibold transition-all duration-200 bg-blue-500 text-white hover:bg-blue-600"
|
|
401
|
+
answerClassName="p-4 rounded-lg font-semibold transition-all duration-200 bg-purple-500 text-white hover:bg-purple-600"
|
|
466
402
|
lineColor="#8b5cf6"
|
|
467
403
|
circleColor="#e9d5ff"
|
|
468
404
|
circleRadius={10}
|
|
@@ -476,7 +412,7 @@ function StyledMatching() {
|
|
|
476
412
|
|
|
477
413
|
```tsx
|
|
478
414
|
import { useState, useEffect } from "react";
|
|
479
|
-
import { Matching } from "react-matchings";
|
|
415
|
+
import { Matching, type TMatch } from "react-matchings";
|
|
480
416
|
import "react-matchings/dist/index.css";
|
|
481
417
|
|
|
482
418
|
function MatchingWithInitialState() {
|
|
@@ -492,18 +428,23 @@ function MatchingWithInitialState() {
|
|
|
492
428
|
{ id: 3, text: "Google" },
|
|
493
429
|
];
|
|
494
430
|
|
|
495
|
-
const [matches, setMatches] = useState([]);
|
|
431
|
+
const [matches, setMatches] = useState<TMatch[]>([]);
|
|
496
432
|
|
|
497
433
|
// Load initial matches (e.g., from localStorage or API)
|
|
498
434
|
useEffect(() => {
|
|
499
435
|
const savedMatches = localStorage.getItem("matches");
|
|
500
436
|
if (savedMatches) {
|
|
501
|
-
|
|
437
|
+
try {
|
|
438
|
+
const parsed: TMatch[] = JSON.parse(savedMatches);
|
|
439
|
+
setMatches(parsed);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error("Failed to parse saved matches:", error);
|
|
442
|
+
}
|
|
502
443
|
}
|
|
503
444
|
}, []);
|
|
504
445
|
|
|
505
446
|
// Save matches when they change
|
|
506
|
-
const handleMatchChange = (newMatches) => {
|
|
447
|
+
const handleMatchChange = (newMatches: TMatch[]): void => {
|
|
507
448
|
setMatches(newMatches);
|
|
508
449
|
localStorage.setItem("matches", JSON.stringify(newMatches));
|
|
509
450
|
};
|
|
@@ -522,7 +463,7 @@ function MatchingWithInitialState() {
|
|
|
522
463
|
|
|
523
464
|
```tsx
|
|
524
465
|
import { useState, useMemo } from "react";
|
|
525
|
-
import { Matching } from "react-matchings";
|
|
466
|
+
import { Matching, type TMatch } from "react-matchings";
|
|
526
467
|
import "react-matchings/dist/index.css";
|
|
527
468
|
|
|
528
469
|
function AssessmentComponent() {
|
|
@@ -539,14 +480,14 @@ function AssessmentComponent() {
|
|
|
539
480
|
];
|
|
540
481
|
|
|
541
482
|
// Correct answers
|
|
542
|
-
const correctMatches = [
|
|
483
|
+
const correctMatches: TMatch[] = [
|
|
543
484
|
{ questionId: 1, answerId: 1 },
|
|
544
485
|
{ questionId: 2, answerId: 2 },
|
|
545
486
|
{ questionId: 3, answerId: 3 },
|
|
546
487
|
];
|
|
547
488
|
|
|
548
|
-
const [matches, setMatches] = useState([]);
|
|
549
|
-
const [showResults, setShowResults] = useState(false);
|
|
489
|
+
const [matches, setMatches] = useState<TMatch[]>([]);
|
|
490
|
+
const [showResults, setShowResults] = useState<boolean>(false);
|
|
550
491
|
|
|
551
492
|
const score = useMemo(() => {
|
|
552
493
|
if (!showResults) return null;
|
|
@@ -614,16 +555,27 @@ You can override default styles using the className props, or customize the colo
|
|
|
614
555
|
|
|
615
556
|
## TypeScript Support
|
|
616
557
|
|
|
617
|
-
This package includes TypeScript definitions.
|
|
558
|
+
This package includes full TypeScript definitions. Import types for type-safe usage:
|
|
618
559
|
|
|
619
560
|
```tsx
|
|
620
|
-
import { Matching } from "react-matchings";
|
|
561
|
+
import { Matching, type TMatch } from "react-matchings";
|
|
562
|
+
|
|
563
|
+
// Use the TMatch type for type safety
|
|
564
|
+
const handleChange = (matches: TMatch[]): void => {
|
|
565
|
+
// matches is properly typed as TMatch[]
|
|
566
|
+
// Each match has questionId: number and answerId: number
|
|
567
|
+
matches.forEach((match) => {
|
|
568
|
+
console.log(match.questionId, match.answerId);
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
The `TMatch` type is defined as:
|
|
621
574
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
// matches is properly typed
|
|
575
|
+
```tsx
|
|
576
|
+
type TMatch = {
|
|
577
|
+
questionId: number;
|
|
578
|
+
answerId: number;
|
|
627
579
|
};
|
|
628
580
|
```
|
|
629
581
|
|
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-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-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)}.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-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-700{background-color:var(--color-gray-700)}.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))}.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)}.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}
|
|
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-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-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-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-700:oklch(37.3% .034 259.733);--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)}.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-gray-300{border-color:var(--color-gray-300)}.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-700{background-color:var(--color-gray-700)}.bg-purple-500{background-color:var(--color-purple-500)}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.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)}.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-white{color:var(--color-white)}.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-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/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
|
-
type
|
|
3
|
+
type TMatch = {
|
|
4
4
|
questionId: number;
|
|
5
5
|
answerId: number;
|
|
6
6
|
};
|
|
@@ -21,8 +21,8 @@ type Props = {
|
|
|
21
21
|
circleRadius?: number;
|
|
22
22
|
offset?: number;
|
|
23
23
|
disabled?: boolean;
|
|
24
|
-
onChange?: (matches:
|
|
24
|
+
onChange?: (matches: TMatch[]) => void;
|
|
25
25
|
};
|
|
26
26
|
declare function Matching({ questions, answers, className, questionClassName, answerClassName, lineColor, circleColor, circleRadius, offset, disabled, onChange, }: Props): react_jsx_runtime.JSX.Element;
|
|
27
27
|
|
|
28
|
-
export { Matching };
|
|
28
|
+
export { Matching, type TMatch };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
|
-
type
|
|
3
|
+
type TMatch = {
|
|
4
4
|
questionId: number;
|
|
5
5
|
answerId: number;
|
|
6
6
|
};
|
|
@@ -21,8 +21,8 @@ type Props = {
|
|
|
21
21
|
circleRadius?: number;
|
|
22
22
|
offset?: number;
|
|
23
23
|
disabled?: boolean;
|
|
24
|
-
onChange?: (matches:
|
|
24
|
+
onChange?: (matches: TMatch[]) => void;
|
|
25
25
|
};
|
|
26
26
|
declare function Matching({ questions, answers, className, questionClassName, answerClassName, lineColor, circleColor, circleRadius, offset, disabled, onChange, }: Props): react_jsx_runtime.JSX.Element;
|
|
27
27
|
|
|
28
|
-
export { Matching };
|
|
28
|
+
export { Matching, type TMatch };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/matching.tsx","../src/lib/utils.ts"],"sourcesContent":["import \"./index.css\";\r\nexport { Matching } from \"./matching\";\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { cn } from \"./lib/utils\";\r\n\r\nexport type Match = {\r\n questionId: number;\r\n answerId: number;\r\n};\r\n\r\ntype Props = {\r\n questions: { id: number; text: string }[];\r\n answers: { id: number; text: string }[];\r\n className?: string;\r\n questionClassName?: string;\r\n answerClassName?: string;\r\n lineColor?: string;\r\n circleColor?: string;\r\n circleRadius?: number;\r\n offset?: number;\r\n disabled?: boolean;\r\n onChange?: (matches: Match[]) => void;\r\n};\r\n\r\ntype Line = {\r\n qId: string;\r\n aId: string;\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n};\r\n\r\nexport function Matching({\r\n questions,\r\n answers,\r\n className,\r\n questionClassName,\r\n answerClassName,\r\n lineColor = \"black\",\r\n circleColor = \"white\",\r\n circleRadius = 8,\r\n offset = 10,\r\n disabled,\r\n onChange,\r\n}: Props) {\r\n const [matches, setMatches] = useState<Record<number, number>>({});\r\n const [lines, setLines] = useState<Line[]>([]);\r\n const [dragging, setDragging] = useState<number | null>(null);\r\n const [dragLine, setDragLine] = useState<{\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n questionId: number;\r\n } | null>(null);\r\n\r\n const containerRef = useRef<HTMLDivElement | null>(null);\r\n const questionRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n const answerRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n\r\n const getElementCenter = useCallback(\r\n (el: HTMLElement | null, isAnswer = false) => {\r\n if (!el || !containerRef.current) return null;\r\n const rect = el.getBoundingClientRect();\r\n const containerRect = containerRef.current.getBoundingClientRect();\r\n return {\r\n x: isAnswer\r\n ? rect.left - containerRect.left + circleRadius + offset\r\n : rect.right - containerRect.left - circleRadius - offset,\r\n y: rect.top + rect.height / 2 - containerRect.top,\r\n };\r\n },\r\n [circleRadius, offset]\r\n );\r\n\r\n const handleMouseDown = (qId: number) => {\r\n if (disabled) return;\r\n setDragging(qId);\r\n requestAnimationFrame(() => {\r\n const start = getElementCenter(questionRefs.current[qId]);\r\n if (start) setDragLine({ start, end: start, questionId: qId });\r\n });\r\n };\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent) => {\r\n if (!dragging || !containerRef.current) return;\r\n const rect = containerRef.current.getBoundingClientRect();\r\n setDragLine((prev) =>\r\n prev\r\n ? {\r\n ...prev,\r\n end: { x: e.clientX - rect.left, y: e.clientY - rect.top },\r\n }\r\n : null\r\n );\r\n },\r\n [dragging]\r\n );\r\n\r\n const handleMouseUp = (aId: number) => {\r\n if (dragging != null) {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev, [dragging]: aId };\r\n if (onChange) {\r\n // notify parent immediately on every change\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n }\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n\r\n const removeMatch = (qId: number) => {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev };\r\n delete newMatches[qId];\r\n if (onChange) {\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n };\r\n\r\n useEffect(() => {\r\n if (!dragging) return;\r\n const handleUp = () => {\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleUp);\r\n return () => {\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleUp);\r\n };\r\n }, [dragging, handleMouseMove]);\r\n\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n const newLines: Line[] = Object.entries(matches)\r\n .map(([qId, aId]) => {\r\n const startEl = questionRefs.current[Number(qId)];\r\n const endEl = answerRefs.current[Number(aId)];\r\n if (!startEl || !endEl) return null;\r\n const start = getElementCenter(startEl, false);\r\n const end = getElementCenter(endEl, true);\r\n if (!start || !end) return null;\r\n return { qId, aId: aId.toString(), start, end };\r\n })\r\n .filter(Boolean) as Line[];\r\n\r\n setLines(newLines);\r\n }, [matches, getElementCenter]);\r\n\r\n return (\r\n <div\r\n className={cn(\"grid relative grid-cols-2 gap-10 select-none\", className)}\r\n ref={containerRef}\r\n >\r\n <svg className=\"absolute z-20 w-full h-full pointer-events-none\">\r\n {lines.map(({ qId, aId, start, end }) => (\r\n <g key={`${qId}-${aId}`}>\r\n <line\r\n x1={start.x}\r\n y1={start.y}\r\n x2={end.x}\r\n y2={end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={start.x}\r\n cy={start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle cx={end.x} cy={end.y} r={circleRadius} fill={circleColor} />\r\n </g>\r\n ))}\r\n {dragLine && (\r\n <g>\r\n <line\r\n x1={dragLine.start.x}\r\n y1={dragLine.start.y}\r\n x2={dragLine.end.x}\r\n y2={dragLine.end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeDasharray=\"5,5\"\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={dragLine.start.x}\r\n cy={dragLine.start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle\r\n cx={dragLine.end.x}\r\n cy={dragLine.end.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n </g>\r\n )}\r\n </svg>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {questions.map((q) => {\r\n const isMatched = matches[q.id] !== undefined;\r\n return (\r\n <button\r\n key={q.id}\r\n ref={(el) => void (questionRefs.current[q.id] = el)}\r\n type=\"button\"\r\n onMouseDown={() => handleMouseDown(q.id)}\r\n onClick={() => isMatched && removeMatch(q.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n questionClassName\r\n )}\r\n >\r\n {q.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {answers.map((a) => {\r\n const isMatched = Object.values(matches).includes(a.id);\r\n return (\r\n <button\r\n key={a.id}\r\n ref={(el) => void (answerRefs.current[a.id] = el)}\r\n type=\"button\"\r\n onMouseUp={() => handleMouseUp(a.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n answerClassName\r\n )}\r\n >\r\n {a.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { clsx, type ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n"],"mappings":"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","names":["aId","qId"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/matching.tsx","../src/lib/utils.ts"],"sourcesContent":["import \"./index.css\";\r\nexport { Matching } from \"./matching\";\r\nexport type { TMatch } from \"./matching\";\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { cn } from \"./lib/utils\";\r\n\r\nexport type TMatch = {\r\n questionId: number;\r\n answerId: number;\r\n};\r\n\r\ntype Props = {\r\n questions: { id: number; text: string }[];\r\n answers: { id: number; text: string }[];\r\n className?: string;\r\n questionClassName?: string;\r\n answerClassName?: string;\r\n lineColor?: string;\r\n circleColor?: string;\r\n circleRadius?: number;\r\n offset?: number;\r\n disabled?: boolean;\r\n onChange?: (matches: TMatch[]) => void;\r\n};\r\n\r\ntype Line = {\r\n qId: string;\r\n aId: string;\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n};\r\n\r\nexport function Matching({\r\n questions,\r\n answers,\r\n className,\r\n questionClassName,\r\n answerClassName,\r\n lineColor = \"black\",\r\n circleColor = \"white\",\r\n circleRadius = 8,\r\n offset = 10,\r\n disabled,\r\n onChange,\r\n}: Props) {\r\n const [matches, setMatches] = useState<Record<number, number>>({});\r\n const [lines, setLines] = useState<Line[]>([]);\r\n const [dragging, setDragging] = useState<number | null>(null);\r\n const [dragLine, setDragLine] = useState<{\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n questionId: number;\r\n } | null>(null);\r\n\r\n const containerRef = useRef<HTMLDivElement | null>(null);\r\n const questionRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n const answerRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n\r\n const getElementCenter = useCallback(\r\n (el: HTMLElement | null, isAnswer = false) => {\r\n if (!el || !containerRef.current) return null;\r\n const rect = el.getBoundingClientRect();\r\n const containerRect = containerRef.current.getBoundingClientRect();\r\n return {\r\n x: isAnswer\r\n ? rect.left - containerRect.left + circleRadius + offset\r\n : rect.right - containerRect.left - circleRadius - offset,\r\n y: rect.top + rect.height / 2 - containerRect.top,\r\n };\r\n },\r\n [circleRadius, offset]\r\n );\r\n\r\n const handleMouseDown = (qId: number) => {\r\n if (disabled) return;\r\n setDragging(qId);\r\n requestAnimationFrame(() => {\r\n const start = getElementCenter(questionRefs.current[qId]);\r\n if (start) setDragLine({ start, end: start, questionId: qId });\r\n });\r\n };\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent) => {\r\n if (!dragging || !containerRef.current) return;\r\n const rect = containerRef.current.getBoundingClientRect();\r\n setDragLine((prev) =>\r\n prev\r\n ? {\r\n ...prev,\r\n end: { x: e.clientX - rect.left, y: e.clientY - rect.top },\r\n }\r\n : null\r\n );\r\n },\r\n [dragging]\r\n );\r\n\r\n const handleMouseUp = (aId: number) => {\r\n if (dragging != null) {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev, [dragging]: aId };\r\n if (onChange) {\r\n // notify parent immediately on every change\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n }\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n\r\n const removeMatch = (qId: number) => {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev };\r\n delete newMatches[qId];\r\n if (onChange) {\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n };\r\n\r\n useEffect(() => {\r\n if (!dragging) return;\r\n const handleUp = () => {\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleUp);\r\n return () => {\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleUp);\r\n };\r\n }, [dragging, handleMouseMove]);\r\n\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n const newLines: Line[] = Object.entries(matches)\r\n .map(([qId, aId]) => {\r\n const startEl = questionRefs.current[Number(qId)];\r\n const endEl = answerRefs.current[Number(aId)];\r\n if (!startEl || !endEl) return null;\r\n const start = getElementCenter(startEl, false);\r\n const end = getElementCenter(endEl, true);\r\n if (!start || !end) return null;\r\n return { qId, aId: aId.toString(), start, end };\r\n })\r\n .filter(Boolean) as Line[];\r\n\r\n setLines(newLines);\r\n }, [matches, getElementCenter]);\r\n\r\n return (\r\n <div\r\n className={cn(\"grid relative grid-cols-2 gap-10 select-none\", className)}\r\n ref={containerRef}\r\n >\r\n <svg className=\"absolute z-20 w-full h-full pointer-events-none\">\r\n {lines.map(({ qId, aId, start, end }) => (\r\n <g key={`${qId}-${aId}`}>\r\n <line\r\n x1={start.x}\r\n y1={start.y}\r\n x2={end.x}\r\n y2={end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={start.x}\r\n cy={start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle cx={end.x} cy={end.y} r={circleRadius} fill={circleColor} />\r\n </g>\r\n ))}\r\n {dragLine && (\r\n <g>\r\n <line\r\n x1={dragLine.start.x}\r\n y1={dragLine.start.y}\r\n x2={dragLine.end.x}\r\n y2={dragLine.end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeDasharray=\"5,5\"\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={dragLine.start.x}\r\n cy={dragLine.start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle\r\n cx={dragLine.end.x}\r\n cy={dragLine.end.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n </g>\r\n )}\r\n </svg>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {questions.map((q) => {\r\n const isMatched = matches[q.id] !== undefined;\r\n return (\r\n <button\r\n key={q.id}\r\n ref={(el) => void (questionRefs.current[q.id] = el)}\r\n type=\"button\"\r\n onMouseDown={() => handleMouseDown(q.id)}\r\n onClick={() => isMatched && removeMatch(q.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n questionClassName\r\n )}\r\n >\r\n {q.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {answers.map((a) => {\r\n const isMatched = Object.values(matches).includes(a.id);\r\n return (\r\n <button\r\n key={a.id}\r\n ref={(el) => void (answerRefs.current[a.id] = el)}\r\n type=\"button\"\r\n onMouseUp={() => handleMouseUp(a.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n answerClassName\r\n )}\r\n >\r\n {a.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { clsx, type ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n"],"mappings":"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","names":["aId","qId"]}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/matching.tsx","../src/lib/utils.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { cn } from \"./lib/utils\";\r\n\r\nexport type Match = {\r\n questionId: number;\r\n answerId: number;\r\n};\r\n\r\ntype Props = {\r\n questions: { id: number; text: string }[];\r\n answers: { id: number; text: string }[];\r\n className?: string;\r\n questionClassName?: string;\r\n answerClassName?: string;\r\n lineColor?: string;\r\n circleColor?: string;\r\n circleRadius?: number;\r\n offset?: number;\r\n disabled?: boolean;\r\n onChange?: (matches: Match[]) => void;\r\n};\r\n\r\ntype Line = {\r\n qId: string;\r\n aId: string;\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n};\r\n\r\nexport function Matching({\r\n questions,\r\n answers,\r\n className,\r\n questionClassName,\r\n answerClassName,\r\n lineColor = \"black\",\r\n circleColor = \"white\",\r\n circleRadius = 8,\r\n offset = 10,\r\n disabled,\r\n onChange,\r\n}: Props) {\r\n const [matches, setMatches] = useState<Record<number, number>>({});\r\n const [lines, setLines] = useState<Line[]>([]);\r\n const [dragging, setDragging] = useState<number | null>(null);\r\n const [dragLine, setDragLine] = useState<{\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n questionId: number;\r\n } | null>(null);\r\n\r\n const containerRef = useRef<HTMLDivElement | null>(null);\r\n const questionRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n const answerRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n\r\n const getElementCenter = useCallback(\r\n (el: HTMLElement | null, isAnswer = false) => {\r\n if (!el || !containerRef.current) return null;\r\n const rect = el.getBoundingClientRect();\r\n const containerRect = containerRef.current.getBoundingClientRect();\r\n return {\r\n x: isAnswer\r\n ? rect.left - containerRect.left + circleRadius + offset\r\n : rect.right - containerRect.left - circleRadius - offset,\r\n y: rect.top + rect.height / 2 - containerRect.top,\r\n };\r\n },\r\n [circleRadius, offset]\r\n );\r\n\r\n const handleMouseDown = (qId: number) => {\r\n if (disabled) return;\r\n setDragging(qId);\r\n requestAnimationFrame(() => {\r\n const start = getElementCenter(questionRefs.current[qId]);\r\n if (start) setDragLine({ start, end: start, questionId: qId });\r\n });\r\n };\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent) => {\r\n if (!dragging || !containerRef.current) return;\r\n const rect = containerRef.current.getBoundingClientRect();\r\n setDragLine((prev) =>\r\n prev\r\n ? {\r\n ...prev,\r\n end: { x: e.clientX - rect.left, y: e.clientY - rect.top },\r\n }\r\n : null\r\n );\r\n },\r\n [dragging]\r\n );\r\n\r\n const handleMouseUp = (aId: number) => {\r\n if (dragging != null) {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev, [dragging]: aId };\r\n if (onChange) {\r\n // notify parent immediately on every change\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n }\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n\r\n const removeMatch = (qId: number) => {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev };\r\n delete newMatches[qId];\r\n if (onChange) {\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n };\r\n\r\n useEffect(() => {\r\n if (!dragging) return;\r\n const handleUp = () => {\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleUp);\r\n return () => {\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleUp);\r\n };\r\n }, [dragging, handleMouseMove]);\r\n\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n const newLines: Line[] = Object.entries(matches)\r\n .map(([qId, aId]) => {\r\n const startEl = questionRefs.current[Number(qId)];\r\n const endEl = answerRefs.current[Number(aId)];\r\n if (!startEl || !endEl) return null;\r\n const start = getElementCenter(startEl, false);\r\n const end = getElementCenter(endEl, true);\r\n if (!start || !end) return null;\r\n return { qId, aId: aId.toString(), start, end };\r\n })\r\n .filter(Boolean) as Line[];\r\n\r\n setLines(newLines);\r\n }, [matches, getElementCenter]);\r\n\r\n return (\r\n <div\r\n className={cn(\"grid relative grid-cols-2 gap-10 select-none\", className)}\r\n ref={containerRef}\r\n >\r\n <svg className=\"absolute z-20 w-full h-full pointer-events-none\">\r\n {lines.map(({ qId, aId, start, end }) => (\r\n <g key={`${qId}-${aId}`}>\r\n <line\r\n x1={start.x}\r\n y1={start.y}\r\n x2={end.x}\r\n y2={end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={start.x}\r\n cy={start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle cx={end.x} cy={end.y} r={circleRadius} fill={circleColor} />\r\n </g>\r\n ))}\r\n {dragLine && (\r\n <g>\r\n <line\r\n x1={dragLine.start.x}\r\n y1={dragLine.start.y}\r\n x2={dragLine.end.x}\r\n y2={dragLine.end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeDasharray=\"5,5\"\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={dragLine.start.x}\r\n cy={dragLine.start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle\r\n cx={dragLine.end.x}\r\n cy={dragLine.end.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n </g>\r\n )}\r\n </svg>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {questions.map((q) => {\r\n const isMatched = matches[q.id] !== undefined;\r\n return (\r\n <button\r\n key={q.id}\r\n ref={(el) => void (questionRefs.current[q.id] = el)}\r\n type=\"button\"\r\n onMouseDown={() => handleMouseDown(q.id)}\r\n onClick={() => isMatched && removeMatch(q.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n questionClassName\r\n )}\r\n >\r\n {q.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {answers.map((a) => {\r\n const isMatched = Object.values(matches).includes(a.id);\r\n return (\r\n <button\r\n key={a.id}\r\n ref={(el) => void (answerRefs.current[a.id] = el)}\r\n type=\"button\"\r\n onMouseUp={() => handleMouseUp(a.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n answerClassName\r\n )}\r\n >\r\n {a.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { clsx, type ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;;;ACAzD,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ADyKU,SACE,KADF;AAjJH,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,eAAe;AAAA,EACf,SAAS;AAAA,EACT;AAAA,EACA;AACF,GAAU;AACR,QAAM,CAAC,SAAS,UAAU,IAAI,SAAiC,CAAC,CAAC;AACjE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAiB,CAAC,CAAC;AAC7C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,SAItB,IAAI;AAEd,QAAM,eAAe,OAA8B,IAAI;AACvD,QAAM,eAAe,OAAiD,CAAC,CAAC;AACxE,QAAM,aAAa,OAAiD,CAAC,CAAC;AAEtE,QAAM,mBAAmB;AAAA,IACvB,CAAC,IAAwB,WAAW,UAAU;AAC5C,UAAI,CAAC,MAAM,CAAC,aAAa,QAAS,QAAO;AACzC,YAAM,OAAO,GAAG,sBAAsB;AACtC,YAAM,gBAAgB,aAAa,QAAQ,sBAAsB;AACjE,aAAO;AAAA,QACL,GAAG,WACC,KAAK,OAAO,cAAc,OAAO,eAAe,SAChD,KAAK,QAAQ,cAAc,OAAO,eAAe;AAAA,QACrD,GAAG,KAAK,MAAM,KAAK,SAAS,IAAI,cAAc;AAAA,MAChD;AAAA,IACF;AAAA,IACA,CAAC,cAAc,MAAM;AAAA,EACvB;AAEA,QAAM,kBAAkB,CAAC,QAAgB;AACvC,QAAI,SAAU;AACd,gBAAY,GAAG;AACf,0BAAsB,MAAM;AAC1B,YAAM,QAAQ,iBAAiB,aAAa,QAAQ,GAAG,CAAC;AACxD,UAAI,MAAO,aAAY,EAAE,OAAO,KAAK,OAAO,YAAY,IAAI,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH;AAEA,QAAM,kBAAkB;AAAA,IACtB,CAAC,MAAkB;AACjB,UAAI,CAAC,YAAY,CAAC,aAAa,QAAS;AACxC,YAAM,OAAO,aAAa,QAAQ,sBAAsB;AACxD;AAAA,QAAY,CAAC,SACX,OACI;AAAA,UACE,GAAG;AAAA,UACH,KAAK,EAAE,GAAG,EAAE,UAAU,KAAK,MAAM,GAAG,EAAE,UAAU,KAAK,IAAI;AAAA,QAC3D,IACA;AAAA,MACN;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,gBAAgB,CAAC,QAAgB;AACrC,QAAI,YAAY,MAAM;AACpB,iBAAW,CAAC,SAAS;AACnB,cAAM,aAAa,EAAE,GAAG,MAAM,CAAC,QAAQ,GAAG,IAAI;AAC9C,YAAI,UAAU;AAEZ;AAAA,YAAe,MACb;AAAA,cACE,OAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,CAAC,KAAKA,IAAG,OAAO;AAAA,gBAC9C,YAAY,OAAO,GAAG;AAAA,gBACtB,UAAUA;AAAA,cACZ,EAAE;AAAA,YACJ;AAAA,UACF;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AACA,gBAAY,IAAI;AAChB,gBAAY,IAAI;AAAA,EAClB;AAEA,QAAM,cAAc,CAAC,QAAgB;AACnC,eAAW,CAAC,SAAS;AACnB,YAAM,aAAa,EAAE,GAAG,KAAK;AAC7B,aAAO,WAAW,GAAG;AACrB,UAAI,UAAU;AACZ;AAAA,UAAe,MACb;AAAA,YACE,OAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,CAACC,MAAK,GAAG,OAAO;AAAA,cAC9C,YAAY,OAAOA,IAAG;AAAA,cACtB,UAAU;AAAA,YACZ,EAAE;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,SAAU;AACf,UAAM,WAAW,MAAM;AACrB,kBAAY,IAAI;AAChB,kBAAY,IAAI;AAAA,IAClB;AACA,aAAS,iBAAiB,aAAa,eAAe;AACtD,aAAS,iBAAiB,WAAW,QAAQ;AAC7C,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,eAAe;AACzD,eAAS,oBAAoB,WAAW,QAAQ;AAAA,IAClD;AAAA,EACF,GAAG,CAAC,UAAU,eAAe,CAAC;AAE9B,YAAU,MAAM;AACd,QAAI,CAAC,aAAa,QAAS;AAE3B,UAAM,WAAmB,OAAO,QAAQ,OAAO,EAC5C,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM;AACnB,YAAM,UAAU,aAAa,QAAQ,OAAO,GAAG,CAAC;AAChD,YAAM,QAAQ,WAAW,QAAQ,OAAO,GAAG,CAAC;AAC5C,UAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAC/B,YAAM,QAAQ,iBAAiB,SAAS,KAAK;AAC7C,YAAM,MAAM,iBAAiB,OAAO,IAAI;AACxC,UAAI,CAAC,SAAS,CAAC,IAAK,QAAO;AAC3B,aAAO,EAAE,KAAK,KAAK,IAAI,SAAS,GAAG,OAAO,IAAI;AAAA,IAChD,CAAC,EACA,OAAO,OAAO;AAEjB,aAAS,QAAQ;AAAA,EACnB,GAAG,CAAC,SAAS,gBAAgB,CAAC;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,gDAAgD,SAAS;AAAA,MACvE,KAAK;AAAA,MAEL;AAAA,6BAAC,SAAI,WAAU,mDACZ;AAAA,gBAAM,IAAI,CAAC,EAAE,KAAK,KAAK,OAAO,IAAI,MACjC,qBAAC,OACC;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,IAAI,MAAM;AAAA,gBACV,IAAI,MAAM;AAAA,gBACV,IAAI,IAAI;AAAA,gBACR,IAAI,IAAI;AAAA,gBACR,QAAQ;AAAA,gBACR,aAAa;AAAA,gBACb,eAAc;AAAA;AAAA,YAChB;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,IAAI,MAAM;AAAA,gBACV,IAAI,MAAM;AAAA,gBACV,GAAG;AAAA,gBACH,MAAM;AAAA;AAAA,YACR;AAAA,YACA,oBAAC,YAAO,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,GAAG,cAAc,MAAM,aAAa;AAAA,eAhB5D,GAAG,GAAG,IAAI,GAAG,EAiBrB,CACD;AAAA,UACA,YACC,qBAAC,OACC;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,IAAI,SAAS,MAAM;AAAA,gBACnB,IAAI,SAAS,MAAM;AAAA,gBACnB,IAAI,SAAS,IAAI;AAAA,gBACjB,IAAI,SAAS,IAAI;AAAA,gBACjB,QAAQ;AAAA,gBACR,aAAa;AAAA,gBACb,iBAAgB;AAAA,gBAChB,eAAc;AAAA;AAAA,YAChB;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,IAAI,SAAS,MAAM;AAAA,gBACnB,IAAI,SAAS,MAAM;AAAA,gBACnB,GAAG;AAAA,gBACH,MAAM;AAAA;AAAA,YACR;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,IAAI,SAAS,IAAI;AAAA,gBACjB,IAAI,SAAS,IAAI;AAAA,gBACjB,GAAG;AAAA,gBACH,MAAM;AAAA;AAAA,YACR;AAAA,aACF;AAAA,WAEJ;AAAA,QAEA,oBAAC,SAAI,WAAU,2BACZ,oBAAU,IAAI,CAAC,MAAM;AACpB,gBAAM,YAAY,QAAQ,EAAE,EAAE,MAAM;AACpC,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,KAAK,CAAC,OAAO,MAAM,aAAa,QAAQ,EAAE,EAAE,IAAI;AAAA,cAChD,MAAK;AAAA,cACL,aAAa,MAAM,gBAAgB,EAAE,EAAE;AAAA,cACvC,SAAS,MAAM,aAAa,YAAY,EAAE,EAAE;AAAA,cAC5C,WAAW;AAAA,gBACT;AAAA,gBACA,aAAa;AAAA,gBACb;AAAA,cACF;AAAA,cAEC,YAAE;AAAA;AAAA,YAXE,EAAE;AAAA,UAYT;AAAA,QAEJ,CAAC,GACH;AAAA,QAEA,oBAAC,SAAI,WAAU,2BACZ,kBAAQ,IAAI,CAAC,MAAM;AAClB,gBAAM,YAAY,OAAO,OAAO,OAAO,EAAE,SAAS,EAAE,EAAE;AACtD,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,KAAK,CAAC,OAAO,MAAM,WAAW,QAAQ,EAAE,EAAE,IAAI;AAAA,cAC9C,MAAK;AAAA,cACL,WAAW,MAAM,cAAc,EAAE,EAAE;AAAA,cACnC,WAAW;AAAA,gBACT;AAAA,gBACA,aAAa;AAAA,gBACb;AAAA,cACF;AAAA,cAEC,YAAE;AAAA;AAAA,YAVE,EAAE;AAAA,UAWT;AAAA,QAEJ,CAAC,GACH;AAAA;AAAA;AAAA,EACF;AAEJ;","names":["aId","qId"]}
|
|
1
|
+
{"version":3,"sources":["../src/matching.tsx","../src/lib/utils.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { cn } from \"./lib/utils\";\r\n\r\nexport type TMatch = {\r\n questionId: number;\r\n answerId: number;\r\n};\r\n\r\ntype Props = {\r\n questions: { id: number; text: string }[];\r\n answers: { id: number; text: string }[];\r\n className?: string;\r\n questionClassName?: string;\r\n answerClassName?: string;\r\n lineColor?: string;\r\n circleColor?: string;\r\n circleRadius?: number;\r\n offset?: number;\r\n disabled?: boolean;\r\n onChange?: (matches: TMatch[]) => void;\r\n};\r\n\r\ntype Line = {\r\n qId: string;\r\n aId: string;\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n};\r\n\r\nexport function Matching({\r\n questions,\r\n answers,\r\n className,\r\n questionClassName,\r\n answerClassName,\r\n lineColor = \"black\",\r\n circleColor = \"white\",\r\n circleRadius = 8,\r\n offset = 10,\r\n disabled,\r\n onChange,\r\n}: Props) {\r\n const [matches, setMatches] = useState<Record<number, number>>({});\r\n const [lines, setLines] = useState<Line[]>([]);\r\n const [dragging, setDragging] = useState<number | null>(null);\r\n const [dragLine, setDragLine] = useState<{\r\n start: { x: number; y: number };\r\n end: { x: number; y: number };\r\n questionId: number;\r\n } | null>(null);\r\n\r\n const containerRef = useRef<HTMLDivElement | null>(null);\r\n const questionRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n const answerRefs = useRef<Record<number, HTMLButtonElement | null>>({});\r\n\r\n const getElementCenter = useCallback(\r\n (el: HTMLElement | null, isAnswer = false) => {\r\n if (!el || !containerRef.current) return null;\r\n const rect = el.getBoundingClientRect();\r\n const containerRect = containerRef.current.getBoundingClientRect();\r\n return {\r\n x: isAnswer\r\n ? rect.left - containerRect.left + circleRadius + offset\r\n : rect.right - containerRect.left - circleRadius - offset,\r\n y: rect.top + rect.height / 2 - containerRect.top,\r\n };\r\n },\r\n [circleRadius, offset]\r\n );\r\n\r\n const handleMouseDown = (qId: number) => {\r\n if (disabled) return;\r\n setDragging(qId);\r\n requestAnimationFrame(() => {\r\n const start = getElementCenter(questionRefs.current[qId]);\r\n if (start) setDragLine({ start, end: start, questionId: qId });\r\n });\r\n };\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent) => {\r\n if (!dragging || !containerRef.current) return;\r\n const rect = containerRef.current.getBoundingClientRect();\r\n setDragLine((prev) =>\r\n prev\r\n ? {\r\n ...prev,\r\n end: { x: e.clientX - rect.left, y: e.clientY - rect.top },\r\n }\r\n : null\r\n );\r\n },\r\n [dragging]\r\n );\r\n\r\n const handleMouseUp = (aId: number) => {\r\n if (dragging != null) {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev, [dragging]: aId };\r\n if (onChange) {\r\n // notify parent immediately on every change\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n }\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n\r\n const removeMatch = (qId: number) => {\r\n setMatches((prev) => {\r\n const newMatches = { ...prev };\r\n delete newMatches[qId];\r\n if (onChange) {\r\n queueMicrotask(() =>\r\n onChange(\r\n Object.entries(newMatches).map(([qId, aId]) => ({\r\n questionId: Number(qId),\r\n answerId: aId,\r\n }))\r\n )\r\n );\r\n }\r\n return newMatches;\r\n });\r\n };\r\n\r\n useEffect(() => {\r\n if (!dragging) return;\r\n const handleUp = () => {\r\n setDragging(null);\r\n setDragLine(null);\r\n };\r\n document.addEventListener(\"mousemove\", handleMouseMove);\r\n document.addEventListener(\"mouseup\", handleUp);\r\n return () => {\r\n document.removeEventListener(\"mousemove\", handleMouseMove);\r\n document.removeEventListener(\"mouseup\", handleUp);\r\n };\r\n }, [dragging, handleMouseMove]);\r\n\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n const newLines: Line[] = Object.entries(matches)\r\n .map(([qId, aId]) => {\r\n const startEl = questionRefs.current[Number(qId)];\r\n const endEl = answerRefs.current[Number(aId)];\r\n if (!startEl || !endEl) return null;\r\n const start = getElementCenter(startEl, false);\r\n const end = getElementCenter(endEl, true);\r\n if (!start || !end) return null;\r\n return { qId, aId: aId.toString(), start, end };\r\n })\r\n .filter(Boolean) as Line[];\r\n\r\n setLines(newLines);\r\n }, [matches, getElementCenter]);\r\n\r\n return (\r\n <div\r\n className={cn(\"grid relative grid-cols-2 gap-10 select-none\", className)}\r\n ref={containerRef}\r\n >\r\n <svg className=\"absolute z-20 w-full h-full pointer-events-none\">\r\n {lines.map(({ qId, aId, start, end }) => (\r\n <g key={`${qId}-${aId}`}>\r\n <line\r\n x1={start.x}\r\n y1={start.y}\r\n x2={end.x}\r\n y2={end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={start.x}\r\n cy={start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle cx={end.x} cy={end.y} r={circleRadius} fill={circleColor} />\r\n </g>\r\n ))}\r\n {dragLine && (\r\n <g>\r\n <line\r\n x1={dragLine.start.x}\r\n y1={dragLine.start.y}\r\n x2={dragLine.end.x}\r\n y2={dragLine.end.y}\r\n stroke={lineColor}\r\n strokeWidth={3}\r\n strokeDasharray=\"5,5\"\r\n strokeLinecap=\"round\"\r\n />\r\n <circle\r\n cx={dragLine.start.x}\r\n cy={dragLine.start.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n <circle\r\n cx={dragLine.end.x}\r\n cy={dragLine.end.y}\r\n r={circleRadius}\r\n fill={circleColor}\r\n />\r\n </g>\r\n )}\r\n </svg>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {questions.map((q) => {\r\n const isMatched = matches[q.id] !== undefined;\r\n return (\r\n <button\r\n key={q.id}\r\n ref={(el) => void (questionRefs.current[q.id] = el)}\r\n type=\"button\"\r\n onMouseDown={() => handleMouseDown(q.id)}\r\n onClick={() => isMatched && removeMatch(q.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n questionClassName\r\n )}\r\n >\r\n {q.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n <div className=\"relative z-10 space-y-3\">\r\n {answers.map((a) => {\r\n const isMatched = Object.values(matches).includes(a.id);\r\n return (\r\n <button\r\n key={a.id}\r\n ref={(el) => void (answerRefs.current[a.id] = el)}\r\n type=\"button\"\r\n onMouseUp={() => handleMouseUp(a.id)}\r\n className={cn(\r\n \"p-4 rounded bg-black text-white w-full font-medium focus:outline-none focus:ring-2 focus:ring-gray-500\",\r\n isMatched && \"bg-gray-700\",\r\n answerClassName\r\n )}\r\n >\r\n {a.text}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { clsx, type ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n"],"mappings":"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","names":["aId","qId"]}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/matching.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { cn } from "./lib/utils";
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type TMatch = {
|
|
5
5
|
questionId: number;
|
|
6
6
|
answerId: number;
|
|
7
7
|
};
|
|
@@ -17,7 +17,7 @@ type Props = {
|
|
|
17
17
|
circleRadius?: number;
|
|
18
18
|
offset?: number;
|
|
19
19
|
disabled?: boolean;
|
|
20
|
-
onChange?: (matches:
|
|
20
|
+
onChange?: (matches: TMatch[]) => void;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
type Line = {
|