@mostajs/qrpanel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ @mostajs/config
2
+ Copyright (C) 2026 Dr Hamid MADANI <drmdh@msn.com>
3
+
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License as published by the Free
6
+ Software Foundation, either version 3 of the License, or (at your option) any
7
+ later version.
8
+
9
+ This program is distributed in the hope that it will be useful, but WITHOUT
10
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
12
+ details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/agpl-3.0.html>.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @mostajs/qrpanel
2
+
3
+ QR code panel — générateur server-side *(PNG / SVG / data URL via la lib `qrcode`)* et composant React `<QrPanel>` *(QR + lien hypertexte copiable + actions copier / ouvrir / mailto)*.
4
+
5
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
6
+
7
+ ## Cross-OS
8
+
9
+ Aucune dépendance native. La lib `qrcode` est pure JS — fonctionne identiquement sur **Linux, macOS, Windows**, en runtime Node ou Edge. Pas de chromium, pas de node-gyp.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @mostajs/qrpanel
15
+ ```
16
+
17
+ ## Usage côté serveur
18
+
19
+ ```ts
20
+ import { generateQrPng, generateQrSvg, generateQrDataUrl } from '@mostajs/qrpanel/server'
21
+
22
+ // Buffer PNG pour streamer dans une route
23
+ const png = await generateQrPng('https://example.com/invite/abc', {
24
+ width: 600,
25
+ errorCorrectionLevel: 'M',
26
+ darkColor: '#2563eb',
27
+ })
28
+
29
+ // SVG pour embed inline
30
+ const svg = await generateQrSvg('https://...', { width: 200 })
31
+
32
+ // Data URL pour <img src> direct
33
+ const dataUrl = await generateQrDataUrl('https://...')
34
+ ```
35
+
36
+ ### Endpoint Next.js (App Router)
37
+
38
+ ```ts
39
+ // app/api/qr/route.ts
40
+ import { NextResponse } from 'next/server'
41
+ import { generateQrPng } from '@mostajs/qrpanel/server'
42
+
43
+ export const runtime = 'nodejs'
44
+
45
+ export async function GET(req: Request) {
46
+ const url = new URL(req.url).searchParams.get('text') ?? ''
47
+ const png = await generateQrPng(url)
48
+ return new NextResponse(png as any, {
49
+ headers: { 'Content-Type': 'image/png' },
50
+ })
51
+ }
52
+ ```
53
+
54
+ ## Usage côté client (React)
55
+
56
+ ```tsx
57
+ import { QrPanel } from '@mostajs/qrpanel/client'
58
+
59
+ <QrPanel
60
+ modes={[
61
+ {
62
+ key: 'direct',
63
+ label: 'Direct',
64
+ url: 'https://app.example.com/projet-x',
65
+ qrSrc: '/api/qr?text=' + encodeURIComponent('https://app.example.com/projet-x'),
66
+ description: 'Le QR mène directement au questionnaire.',
67
+ },
68
+ {
69
+ key: 'invite',
70
+ label: 'Invite token',
71
+ url: 'https://app.example.com/invite/eyJ...',
72
+ qrSrc: '/api/qr?text=' + encodeURIComponent('https://app.example.com/invite/eyJ...'),
73
+ description: 'Token signé valable 60 jours, idéal pour mailing.',
74
+ },
75
+ ]}
76
+ title="QR code & lien d'invitation"
77
+ mailSubject="Invitation au questionnaire"
78
+ />
79
+ ```
80
+
81
+ ### Avec un seul mode
82
+
83
+ ```tsx
84
+ <QrPanel modes={[
85
+ { key: 'd', label: 'Direct', url, qrSrc },
86
+ ]} />
87
+ ```
88
+
89
+ Le toggle de modes est masqué automatiquement quand `modes.length === 1`.
90
+
91
+ ## API
92
+
93
+ ### Server
94
+
95
+ | Fonction | Retour | Cas |
96
+ |----------|--------|-----|
97
+ | `generateQrPng(text, opts)` | `Promise<Buffer>` | Stream PNG dans une route |
98
+ | `generateQrSvg(text, opts)` | `Promise<string>` | Embed inline, scalable |
99
+ | `generateQrDataUrl(text, opts)` | `Promise<string>` | `<img src>` direct |
100
+
101
+ ### Options communes
102
+
103
+ ```ts
104
+ interface QrOptions {
105
+ width?: number // px, default 600
106
+ margin?: number // modules, default 2
107
+ errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H' // default 'M'
108
+ darkColor?: string // hex, default '#0f172a'
109
+ lightColor?: string // hex, default '#ffffff'
110
+ }
111
+ ```
112
+
113
+ ### Client `<QrPanel>`
114
+
115
+ ```ts
116
+ interface QrPanelMode {
117
+ key: string // identifiant interne
118
+ label: string // bouton
119
+ url: string // URL textuelle copiable
120
+ qrSrc: string // src de l'image QR
121
+ description?: string
122
+ }
123
+
124
+ interface QrPanelProps {
125
+ modes: QrPanelMode[]
126
+ initialModeIndex?: number // default 0
127
+ title?: string
128
+ mailSubject?: string // default 'Invitation'
129
+ mailBodyTemplate?: string // {url} = placeholder
130
+ qrSize?: number // px, default 260
131
+ className?: string
132
+ }
133
+ ```
134
+
135
+ ## Style
136
+
137
+ Le composant utilise des classes Tailwind v3+. L'app consumer doit avoir Tailwind dans son pipeline. Les classes sont des chaînes, le composant reste portable *(utilisable même si l'app override le CSS)*.
138
+
139
+ ## Licence
140
+
141
+ AGPL-3.0-or-later.
@@ -0,0 +1,30 @@
1
+ export interface QrPanelMode {
2
+ /** Identifiant interne du mode (libre — sera passé en argument à `qrSrc`). */
3
+ key: string;
4
+ /** Libellé bouton. */
5
+ label: string;
6
+ /** URL absolue à afficher / copier dans ce mode. */
7
+ url: string;
8
+ /** URL de l'image QR pour ce mode (endpoint API ou data URL). */
9
+ qrSrc: string;
10
+ /** Description courte affichée sous l'en-tête. */
11
+ description?: string;
12
+ }
13
+ export interface QrPanelProps {
14
+ /** Liste des modes proposés (1 ou plusieurs). */
15
+ modes: QrPanelMode[];
16
+ /** Mode initial — index dans `modes`. Default 0. */
17
+ initialModeIndex?: number;
18
+ /** Titre du panel. Default 'QR code & lien d\'invitation'. */
19
+ title?: string;
20
+ /** Pré-remplissage `mailto`. Default 'Invitation'. */
21
+ mailSubject?: string;
22
+ /** Pré-remplissage `mailto` body — `{url}` est remplacé par l'URL courante. */
23
+ mailBodyTemplate?: string;
24
+ /** Taille de l'image QR. Default 260. */
25
+ qrSize?: number;
26
+ /** Classe CSS racine optionnelle. */
27
+ className?: string;
28
+ }
29
+ export declare function QrPanel({ modes, initialModeIndex, title, mailSubject, mailBodyTemplate, qrSize, className, }: QrPanelProps): import("react/jsx-runtime").JSX.Element | null;
30
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAA;IACX,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAID,wBAAgB,OAAO,CAAC,EACtB,KAAK,EAAE,gBAAoB,EAAE,KAAsC,EACnE,WAA0B,EAAE,gBAA+B,EAC3D,MAAY,EAAE,SAAc,GAC7B,EAAE,YAAY,kDAkGd"}
package/dist/client.js ADDED
@@ -0,0 +1,40 @@
1
+ // @mostajs/qrpanel/client — React component
2
+ // Author: Dr Hamid MADANI <drmdh@msn.com>
3
+ //
4
+ // Composant `<QrPanel>` agnostique de l'app. Affiche un QR + l'URL
5
+ // textuelle copiable + 3 actions (copier, ouvrir, mailto).
6
+ // Le composant ne génère pas le QR lui-même : l'app fournit le `qrSrc`
7
+ // (URL d'un endpoint API ou data URL inline).
8
+ //
9
+ // Style : Tailwind classes — l'app les compile dans son build (le
10
+ // component reste portable car les classes sont des chaînes).
11
+ //
12
+ // Note: marqué 'use client' pour Next.js App Router. Pour SPA (Vite,
13
+ // CRA, etc.), la directive est ignorée — le component fonctionne tel
14
+ // quel.
15
+ 'use client';
16
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
+ import { useState, useId } from 'react';
18
+ const DEFAULT_BODY = 'Bonjour,\n\nVoici ton lien personnel :\n\n{url}\n\n';
19
+ export function QrPanel({ modes, initialModeIndex = 0, title = 'QR code & lien d\'invitation', mailSubject = 'Invitation', mailBodyTemplate = DEFAULT_BODY, qrSize = 260, className = '', }) {
20
+ const [idx, setIdx] = useState(Math.min(Math.max(0, initialModeIndex), modes.length - 1));
21
+ const [copied, setCopied] = useState(false);
22
+ const textareaId = useId();
23
+ const current = modes[idx] ?? modes[0];
24
+ if (!current)
25
+ return null;
26
+ const mailto = `mailto:?subject=${encodeURIComponent(mailSubject)}&body=${encodeURIComponent(mailBodyTemplate.replace('{url}', current.url))}`;
27
+ async function copy() {
28
+ try {
29
+ await navigator.clipboard.writeText(current.url);
30
+ setCopied(true);
31
+ setTimeout(() => setCopied(false), 2000);
32
+ }
33
+ catch {
34
+ const el = document.getElementById(textareaId);
35
+ el?.select();
36
+ }
37
+ }
38
+ return (_jsxs("div", { className: 'bg-white border border-slate-200 rounded-xl p-4 space-y-3 ' + className, children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("h2", { className: "font-semibold text-slate-900", children: title }), modes.length > 1 && (_jsx("div", { className: "flex items-center gap-1 text-xs", children: modes.map((m, i) => (_jsx("button", { type: "button", onClick: () => setIdx(i), className: 'px-2 py-1 rounded ' + (i === idx ? 'bg-blue-600 text-white' : 'bg-slate-100 hover:bg-slate-200'), children: m.label }, m.key))) }))] }), current.description && (_jsx("p", { className: "text-xs text-slate-500", children: current.description })), _jsxs("div", { className: "grid md:grid-cols-2 gap-4 items-start", children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("img", { src: current.qrSrc, alt: `QR ${current.label}`, width: qrSize, height: qrSize, className: "border border-slate-200 rounded-lg" }), _jsx("a", { href: current.qrSrc, download: `qr-${current.key}.png`, className: "px-3 py-1.5 rounded text-xs bg-emerald-50 hover:bg-emerald-100 text-emerald-700 border border-emerald-200", children: "\u2B07 T\u00E9l\u00E9charger le PNG" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "block text-xs font-medium text-slate-700 mb-1", children: "Lien direct *(\u00E0 coller dans un email)*" }), _jsx("textarea", { id: textareaId, readOnly: true, value: current.url, rows: current.url.length > 60 ? 4 : 2, onClick: (e) => e.currentTarget.select(), className: "w-full px-2 py-1.5 border border-slate-300 rounded text-xs font-mono bg-slate-50 break-all" })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("button", { type: "button", onClick: copy, className: "px-3 py-1.5 rounded text-xs bg-blue-600 text-white hover:bg-blue-700", children: copied ? '✓ Copié' : '📋 Copier le lien' }), _jsx("a", { href: current.url, target: "_blank", rel: "noreferrer", className: "px-3 py-1.5 rounded text-xs bg-slate-100 hover:bg-slate-200 text-slate-700", children: "\u2197 Ouvrir" }), _jsx("a", { href: mailto, className: "px-3 py-1.5 rounded text-xs bg-amber-50 hover:bg-amber-100 text-amber-700 border border-amber-200", children: "\u2709 Pr\u00E9-remplir un mail" })] })] })] })] }));
39
+ }
40
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './server.js';
2
+ export * from './client.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // @mostajs/qrpanel — barrel
2
+ // Author: Dr Hamid MADANI <drmdh@msn.com>
3
+ //
4
+ // Re-export complet pour les consumers qui ne séparent pas client/server.
5
+ // Préfère `@mostajs/qrpanel/server` ou `@mostajs/qrpanel/client` pour
6
+ // éviter d'inclure React côté serveur ou qrcode côté client.
7
+ export * from './server.js';
8
+ export * from './client.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,0CAA0C;AAC1C,EAAE;AACF,0EAA0E;AAC1E,sEAAsE;AACtE,6DAA6D;AAE7D,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,27 @@
1
+ export interface QrOptions {
2
+ /** Largeur/hauteur de l'image en pixels. Default 600. */
3
+ width?: number;
4
+ /** Marge blanche autour du QR (en modules). Default 2. */
5
+ margin?: number;
6
+ /**
7
+ * Niveau de correction d'erreur. Plus élevé = plus de redondance →
8
+ * QR plus dense mais résistant aux occlusions / impressions.
9
+ * L = 7 % (default qrcode lib)
10
+ * M = 15 % (recommandé pour usage écran)
11
+ * Q = 25 %
12
+ * H = 30 % (recommandé si logo overlay)
13
+ */
14
+ errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
15
+ /** Couleur des modules sombres. Default '#0f172a' (slate-900). */
16
+ darkColor?: string;
17
+ /** Couleur du fond. Default '#ffffff'. */
18
+ lightColor?: string;
19
+ }
20
+ /** Génère un PNG (Buffer) d'un QR code encodant `text`. */
21
+ export declare function generateQrPng(text: string, opts?: QrOptions): Promise<Buffer>;
22
+ /** Génère un SVG (string) d'un QR code encodant `text`. */
23
+ export declare function generateQrSvg(text: string, opts?: QrOptions): Promise<string>;
24
+ /** Génère un Data URL `data:image/png;base64,...` — pratique pour
25
+ * embarquer directement dans une balise `<img src>` sans endpoint dédié. */
26
+ export declare function generateQrDataUrl(text: string, opts?: QrOptions): Promise<string>;
27
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IAC5C,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,SAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAUjF;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,SAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAWjF;AAED;6EAC6E;AAC7E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,SAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAUrF"}
package/dist/server.js ADDED
@@ -0,0 +1,45 @@
1
+ // @mostajs/qrpanel/server — server-side QR generation (Node)
2
+ // Author: Dr Hamid MADANI <drmdh@msn.com>
3
+ //
4
+ // Wrap minimaliste de la lib `qrcode` (npm). Cross-OS natif (Linux,
5
+ // macOS, Windows) — pas de chromium/puppeteer requis.
6
+ import QRCode from 'qrcode';
7
+ /** Génère un PNG (Buffer) d'un QR code encodant `text`. */
8
+ export function generateQrPng(text, opts = {}) {
9
+ return QRCode.toBuffer(text, {
10
+ width: opts.width ?? 600,
11
+ margin: opts.margin ?? 2,
12
+ errorCorrectionLevel: opts.errorCorrectionLevel ?? 'M',
13
+ color: {
14
+ dark: opts.darkColor ?? '#0f172a',
15
+ light: opts.lightColor ?? '#ffffff',
16
+ },
17
+ });
18
+ }
19
+ /** Génère un SVG (string) d'un QR code encodant `text`. */
20
+ export function generateQrSvg(text, opts = {}) {
21
+ return QRCode.toString(text, {
22
+ type: 'svg',
23
+ width: opts.width ?? 600,
24
+ margin: opts.margin ?? 2,
25
+ errorCorrectionLevel: opts.errorCorrectionLevel ?? 'M',
26
+ color: {
27
+ dark: opts.darkColor ?? '#0f172a',
28
+ light: opts.lightColor ?? '#ffffff',
29
+ },
30
+ });
31
+ }
32
+ /** Génère un Data URL `data:image/png;base64,...` — pratique pour
33
+ * embarquer directement dans une balise `<img src>` sans endpoint dédié. */
34
+ export function generateQrDataUrl(text, opts = {}) {
35
+ return QRCode.toDataURL(text, {
36
+ width: opts.width ?? 600,
37
+ margin: opts.margin ?? 2,
38
+ errorCorrectionLevel: opts.errorCorrectionLevel ?? 'M',
39
+ color: {
40
+ dark: opts.darkColor ?? '#0f172a',
41
+ light: opts.lightColor ?? '#ffffff',
42
+ },
43
+ });
44
+ }
45
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,0CAA0C;AAC1C,EAAE;AACF,oEAAoE;AACpE,sDAAsD;AAEtD,OAAO,MAAM,MAAM,QAAQ,CAAA;AAsB3B,2DAA2D;AAC3D,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAkB,EAAE;IAC9D,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QAC3B,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,GAAG;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC;QACxB,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,IAAI,GAAG;QACtD,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;YACjC,KAAK,EAAE,IAAI,CAAC,UAAU,IAAI,SAAS;SACpC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAkB,EAAE;IAC9D,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QAC3B,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,GAAG;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC;QACxB,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,IAAI,GAAG;QACtD,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;YACjC,KAAK,EAAE,IAAI,CAAC,UAAU,IAAI,SAAS;SACpC;KACF,CAAC,CAAA;AACJ,CAAC;AAED;6EAC6E;AAC7E,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,OAAkB,EAAE;IAClE,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE;QAC5B,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,GAAG;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC;QACxB,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,IAAI,GAAG;QACtD,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;YACjC,KAAK,EAAE,IAAI,CAAC,UAAU,IAAI,SAAS;SACpC;KACF,CAAC,CAAA;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@mostajs/qrpanel",
3
+ "version": "0.1.0",
4
+ "description": "QR code panel — server-side PNG generator (qrcode npm, cross-OS, no chromium) + React client component <QrPanel> with copy/share/mailto.",
5
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
+ "license": "AGPL-3.0-or-later",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./server": {
17
+ "types": "./dist/server.d.ts",
18
+ "import": "./dist/server.js",
19
+ "default": "./dist/server.js"
20
+ },
21
+ "./client": {
22
+ "types": "./dist/client.d.ts",
23
+ "import": "./dist/client.js",
24
+ "default": "./dist/client.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "LICENSE",
30
+ "README.md"
31
+ ],
32
+ "keywords": [
33
+ "qrcode",
34
+ "qr",
35
+ "react",
36
+ "invite",
37
+ "magic-link"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "dev": "tsc --watch",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "dependencies": {
45
+ "qrcode": "^1.5.4"
46
+ },
47
+ "peerDependencies": {
48
+ "react": ">=18 <20"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "react": { "optional": true }
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^22.0.0",
55
+ "@types/qrcode": "^1.5.5",
56
+ "@types/react": "^19.0.0",
57
+ "react": "^19.0.0",
58
+ "typescript": "^5.6.0"
59
+ }
60
+ }