@zentauri-ui/zentauri-components 2.3.0 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/cli/props.json +2508 -1458
- package/cli/registry.json +20 -0
- package/dist/chunk-2RBBXVFA.js +19 -0
- package/dist/{chunk-ENKXB2BA.js.map → chunk-2RBBXVFA.js.map} +1 -1
- package/dist/{chunk-BFHJF4MV.mjs → chunk-425DAZTS.mjs} +4 -4
- package/dist/{chunk-BFHJF4MV.mjs.map → chunk-425DAZTS.mjs.map} +1 -1
- package/dist/chunk-4U6PVVST.mjs +15 -0
- package/dist/chunk-4U6PVVST.mjs.map +1 -0
- package/dist/{chunk-AARJLZXP.js → chunk-6GP74P4F.js} +6 -6
- package/dist/{chunk-AARJLZXP.js.map → chunk-6GP74P4F.js.map} +1 -1
- package/dist/{chunk-DIAA5VH4.mjs → chunk-CIZQQ32L.mjs} +3 -3
- package/dist/chunk-CIZQQ32L.mjs.map +1 -0
- package/dist/chunk-DUKHIN2W.js +44 -0
- package/dist/chunk-DUKHIN2W.js.map +1 -0
- package/dist/{chunk-PQ2XTY3M.js → chunk-G36ZV446.js} +13 -13
- package/dist/{chunk-PQ2XTY3M.js.map → chunk-G36ZV446.js.map} +1 -1
- package/dist/chunk-HJ7EFBED.js +86 -0
- package/dist/chunk-HJ7EFBED.js.map +1 -0
- package/dist/{chunk-H3BJOK22.js → chunk-HNAUUCR5.js} +3 -3
- package/dist/chunk-HNAUUCR5.js.map +1 -0
- package/dist/chunk-K2VCZK4I.mjs +75 -0
- package/dist/chunk-K2VCZK4I.mjs.map +1 -0
- package/dist/{chunk-DSX6RUYI.js → chunk-KVBFWRPF.js} +12 -12
- package/dist/{chunk-DSX6RUYI.js.map → chunk-KVBFWRPF.js.map} +1 -1
- package/dist/chunk-N4MLFU2Q.mjs +69 -0
- package/dist/chunk-N4MLFU2Q.mjs.map +1 -0
- package/dist/{chunk-ATE5SCTR.mjs → chunk-N6Q4ZLQR.mjs} +3 -3
- package/dist/{chunk-ATE5SCTR.mjs.map → chunk-N6Q4ZLQR.mjs.map} +1 -1
- package/dist/{chunk-YY7G4NV3.js → chunk-NZQA4M35.js} +49 -17
- package/dist/chunk-NZQA4M35.js.map +1 -0
- package/dist/chunk-OME7XOPN.js +78 -0
- package/dist/chunk-OME7XOPN.js.map +1 -0
- package/dist/chunk-PVDWJUMF.mjs +34 -0
- package/dist/chunk-PVDWJUMF.mjs.map +1 -0
- package/dist/chunk-PVVYOIU2.js +38 -0
- package/dist/chunk-PVVYOIU2.js.map +1 -0
- package/dist/chunk-PWEZB53R.js +90 -0
- package/dist/chunk-PWEZB53R.js.map +1 -0
- package/dist/chunk-PWYEC3KY.mjs +30 -0
- package/dist/chunk-PWYEC3KY.mjs.map +1 -0
- package/dist/{chunk-JKKF5DCF.mjs → chunk-R2JXARKB.mjs} +3 -3
- package/dist/{chunk-JKKF5DCF.mjs.map → chunk-R2JXARKB.mjs.map} +1 -1
- package/dist/chunk-SD7YXMNV.js +40 -0
- package/dist/chunk-SD7YXMNV.js.map +1 -0
- package/dist/{chunk-ZB6C6CJQ.mjs → chunk-TZTUL6C4.mjs} +40 -8
- package/dist/chunk-TZTUL6C4.mjs.map +1 -0
- package/dist/chunk-UBQY572I.mjs +81 -0
- package/dist/chunk-UBQY572I.mjs.map +1 -0
- package/dist/chunk-V5EE5ATH.mjs +36 -0
- package/dist/chunk-V5EE5ATH.mjs.map +1 -0
- package/dist/chunk-XUK5P37Y.js +19 -0
- package/dist/chunk-XUK5P37Y.js.map +1 -0
- package/dist/{chunk-WZY32L6K.mjs → chunk-YDD5HQGX.mjs} +3 -3
- package/dist/{chunk-WZY32L6K.mjs.map → chunk-YDD5HQGX.mjs.map} +1 -1
- package/dist/design-system/facade.js +12 -8
- package/dist/design-system/facade.js.map +1 -1
- package/dist/design-system/facade.mjs +11 -7
- package/dist/design-system/facade.mjs.map +1 -1
- package/dist/design-system/index.d.ts +4 -0
- package/dist/design-system/index.d.ts.map +1 -1
- package/dist/design-system/qr-code.d.ts +4 -0
- package/dist/design-system/qr-code.d.ts.map +1 -0
- package/dist/design-system/qr-scanner.d.ts +11 -0
- package/dist/design-system/qr-scanner.d.ts.map +1 -0
- package/dist/design-system/secret-reveal.d.ts +1 -1
- package/dist/design-system/secret-reveal.d.ts.map +1 -1
- package/dist/design-system/speech-recognition.d.ts +39 -0
- package/dist/design-system/speech-recognition.d.ts.map +1 -0
- package/dist/design-system/speech-synthesizer.d.ts +41 -0
- package/dist/design-system/speech-synthesizer.d.ts.map +1 -0
- package/dist/ui/buttons/animated.js +14 -10
- package/dist/ui/buttons/animated.js.map +1 -1
- package/dist/ui/buttons/animated.mjs +12 -8
- package/dist/ui/buttons/animated.mjs.map +1 -1
- package/dist/ui/buttons.js +15 -11
- package/dist/ui/buttons.mjs +13 -9
- package/dist/ui/data-table.js +25 -21
- package/dist/ui/data-table.js.map +1 -1
- package/dist/ui/data-table.mjs +15 -11
- package/dist/ui/data-table.mjs.map +1 -1
- package/dist/ui/dynamic-stepper.js +24 -20
- package/dist/ui/dynamic-stepper.js.map +1 -1
- package/dist/ui/dynamic-stepper.mjs +13 -9
- package/dist/ui/dynamic-stepper.mjs.map +1 -1
- package/dist/ui/pagination.js +16 -12
- package/dist/ui/pagination.mjs +13 -9
- package/dist/ui/qr-code/animated/animations.d.ts +8 -0
- package/dist/ui/qr-code/animated/animations.d.ts.map +1 -0
- package/dist/ui/qr-code/animated/index.d.ts +4 -0
- package/dist/ui/qr-code/animated/index.d.ts.map +1 -0
- package/dist/ui/qr-code/animated/qr-code-animated.d.ts +6 -0
- package/dist/ui/qr-code/animated/qr-code-animated.d.ts.map +1 -0
- package/dist/ui/qr-code/animated/types.d.ts +9 -0
- package/dist/ui/qr-code/animated/types.d.ts.map +1 -0
- package/dist/ui/qr-code/animated.js +156 -0
- package/dist/ui/qr-code/animated.js.map +1 -0
- package/dist/ui/qr-code/animated.mjs +149 -0
- package/dist/ui/qr-code/animated.mjs.map +1 -0
- package/dist/ui/qr-code/index.d.ts +5 -0
- package/dist/ui/qr-code/index.d.ts.map +1 -0
- package/dist/ui/qr-code/qr-code-base.d.ts +47 -0
- package/dist/ui/qr-code/qr-code-base.d.ts.map +1 -0
- package/dist/ui/qr-code/qr-code.d.ts +2 -0
- package/dist/ui/qr-code/qr-code.d.ts.map +1 -0
- package/dist/ui/qr-code/types.d.ts +14 -0
- package/dist/ui/qr-code/types.d.ts.map +1 -0
- package/dist/ui/qr-code/variants.d.ts +4 -0
- package/dist/ui/qr-code/variants.d.ts.map +1 -0
- package/dist/ui/qr-code.js +35 -0
- package/dist/ui/qr-code.js.map +1 -0
- package/dist/ui/qr-code.mjs +17 -0
- package/dist/ui/qr-code.mjs.map +1 -0
- package/dist/ui/qr-scanner/index.d.ts +4 -0
- package/dist/ui/qr-scanner/index.d.ts.map +1 -0
- package/dist/ui/qr-scanner/qr-scanner-base.d.ts +62 -0
- package/dist/ui/qr-scanner/qr-scanner-base.d.ts.map +1 -0
- package/dist/ui/qr-scanner/qr-scanner.d.ts +2 -0
- package/dist/ui/qr-scanner/qr-scanner.d.ts.map +1 -0
- package/dist/ui/qr-scanner/types.d.ts +28 -0
- package/dist/ui/qr-scanner/types.d.ts.map +1 -0
- package/dist/ui/qr-scanner/variants.d.ts +9 -0
- package/dist/ui/qr-scanner/variants.d.ts.map +1 -0
- package/dist/ui/qr-scanner.js +316 -0
- package/dist/ui/qr-scanner.js.map +1 -0
- package/dist/ui/qr-scanner.mjs +308 -0
- package/dist/ui/qr-scanner.mjs.map +1 -0
- package/dist/ui/secret-reveal/animated/secret-reveal-animated.d.ts.map +1 -1
- package/dist/ui/secret-reveal/animated.js +10 -7
- package/dist/ui/secret-reveal/animated.js.map +1 -1
- package/dist/ui/secret-reveal/animated.mjs +6 -3
- package/dist/ui/secret-reveal/animated.mjs.map +1 -1
- package/dist/ui/secret-reveal/secret-reveal-base.d.ts.map +1 -1
- package/dist/ui/secret-reveal.js +14 -11
- package/dist/ui/secret-reveal.js.map +1 -1
- package/dist/ui/secret-reveal.mjs +7 -4
- package/dist/ui/secret-reveal.mjs.map +1 -1
- package/dist/ui/speech-recognition/animated/animations.d.ts +8 -0
- package/dist/ui/speech-recognition/animated/animations.d.ts.map +1 -0
- package/dist/ui/speech-recognition/animated/index.d.ts +4 -0
- package/dist/ui/speech-recognition/animated/index.d.ts.map +1 -0
- package/dist/ui/speech-recognition/animated/speech-recognition-animated.d.ts +6 -0
- package/dist/ui/speech-recognition/animated/speech-recognition-animated.d.ts.map +1 -0
- package/dist/ui/speech-recognition/animated/types.d.ts +9 -0
- package/dist/ui/speech-recognition/animated/types.d.ts.map +1 -0
- package/dist/ui/speech-recognition/animated.js +288 -0
- package/dist/ui/speech-recognition/animated.js.map +1 -0
- package/dist/ui/speech-recognition/animated.mjs +285 -0
- package/dist/ui/speech-recognition/animated.mjs.map +1 -0
- package/dist/ui/speech-recognition/index.d.ts +4 -0
- package/dist/ui/speech-recognition/index.d.ts.map +1 -0
- package/dist/ui/speech-recognition/speech-recognition-base.d.ts +6 -0
- package/dist/ui/speech-recognition/speech-recognition-base.d.ts.map +1 -0
- package/dist/ui/speech-recognition/speech-recognition.d.ts +2 -0
- package/dist/ui/speech-recognition/speech-recognition.d.ts.map +1 -0
- package/dist/ui/speech-recognition/types.d.ts +31 -0
- package/dist/ui/speech-recognition/types.d.ts.map +1 -0
- package/dist/ui/speech-recognition/variants.d.ts +11 -0
- package/dist/ui/speech-recognition/variants.d.ts.map +1 -0
- package/dist/ui/speech-recognition.js +242 -0
- package/dist/ui/speech-recognition.js.map +1 -0
- package/dist/ui/speech-recognition.mjs +233 -0
- package/dist/ui/speech-recognition.mjs.map +1 -0
- package/dist/ui/speech-synthesizer/animated/animations.d.ts +8 -0
- package/dist/ui/speech-synthesizer/animated/animations.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/animated/index.d.ts +4 -0
- package/dist/ui/speech-synthesizer/animated/index.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/animated/speech-synthesizer-animated.d.ts +6 -0
- package/dist/ui/speech-synthesizer/animated/speech-synthesizer-animated.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/animated/types.d.ts +9 -0
- package/dist/ui/speech-synthesizer/animated/types.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/animated.js +269 -0
- package/dist/ui/speech-synthesizer/animated.js.map +1 -0
- package/dist/ui/speech-synthesizer/animated.mjs +266 -0
- package/dist/ui/speech-synthesizer/animated.mjs.map +1 -0
- package/dist/ui/speech-synthesizer/index.d.ts +4 -0
- package/dist/ui/speech-synthesizer/index.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/speech-synthesizer-base.d.ts +6 -0
- package/dist/ui/speech-synthesizer/speech-synthesizer-base.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/speech-synthesizer.d.ts +2 -0
- package/dist/ui/speech-synthesizer/speech-synthesizer.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/types.d.ts +43 -0
- package/dist/ui/speech-synthesizer/types.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer/variants.d.ts +13 -0
- package/dist/ui/speech-synthesizer/variants.d.ts.map +1 -0
- package/dist/ui/speech-synthesizer.js +220 -0
- package/dist/ui/speech-synthesizer.js.map +1 -0
- package/dist/ui/speech-synthesizer.mjs +211 -0
- package/dist/ui/speech-synthesizer.mjs.map +1 -0
- package/dist/ui/split-button.js +26 -22
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/split-button.mjs +13 -9
- package/dist/ui/split-button.mjs.map +1 -1
- package/package.json +5 -2
- package/src/design-system/index.ts +4 -0
- package/src/design-system/qr-code.ts +13 -0
- package/src/design-system/qr-scanner.ts +32 -0
- package/src/design-system/secret-reveal.ts +1 -1
- package/src/design-system/speech-recognition.ts +82 -0
- package/src/design-system/speech-synthesizer.ts +90 -0
- package/src/ui/qr-code/animated/animations.ts +51 -0
- package/src/ui/qr-code/animated/index.ts +5 -0
- package/src/ui/qr-code/animated/qr-code-animated.tsx +111 -0
- package/src/ui/qr-code/animated/types.ts +10 -0
- package/src/ui/qr-code/index.ts +10 -0
- package/src/ui/qr-code/qr-code-base.tsx +149 -0
- package/src/ui/qr-code/qr-code.test.tsx +58 -0
- package/src/ui/qr-code/qr-code.tsx +2 -0
- package/src/ui/qr-code/types.ts +22 -0
- package/src/ui/qr-code/variants.ts +11 -0
- package/src/ui/qr-scanner/index.ts +17 -0
- package/src/ui/qr-scanner/qr-scanner-base.tsx +568 -0
- package/src/ui/qr-scanner/qr-scanner.test.tsx +61 -0
- package/src/ui/qr-scanner/qr-scanner.tsx +2 -0
- package/src/ui/qr-scanner/types.ts +32 -0
- package/src/ui/qr-scanner/variants.ts +26 -0
- package/src/ui/secret-reveal/animated/secret-reveal-animated.tsx +4 -1
- package/src/ui/secret-reveal/secret-reveal-base.tsx +4 -1
- package/src/ui/speech-recognition/animated/animations.ts +62 -0
- package/src/ui/speech-recognition/animated/index.ts +8 -0
- package/src/ui/speech-recognition/animated/speech-recognition-animated.tsx +276 -0
- package/src/ui/speech-recognition/animated/types.ts +11 -0
- package/src/ui/speech-recognition/index.ts +15 -0
- package/src/ui/speech-recognition/speech-recognition-base.tsx +276 -0
- package/src/ui/speech-recognition/speech-recognition.test.tsx +74 -0
- package/src/ui/speech-recognition/speech-recognition.tsx +1 -0
- package/src/ui/speech-recognition/types.ts +50 -0
- package/src/ui/speech-recognition/variants.ts +47 -0
- package/src/ui/speech-synthesizer/animated/animations.ts +62 -0
- package/src/ui/speech-synthesizer/animated/index.ts +8 -0
- package/src/ui/speech-synthesizer/animated/speech-synthesizer-animated.tsx +260 -0
- package/src/ui/speech-synthesizer/animated/types.ts +11 -0
- package/src/ui/speech-synthesizer/index.ts +14 -0
- package/src/ui/speech-synthesizer/speech-synthesizer-base.tsx +255 -0
- package/src/ui/speech-synthesizer/speech-synthesizer.test.tsx +87 -0
- package/src/ui/speech-synthesizer/speech-synthesizer.tsx +1 -0
- package/src/ui/speech-synthesizer/types.ts +57 -0
- package/src/ui/speech-synthesizer/variants.ts +55 -0
- package/dist/chunk-DIAA5VH4.mjs.map +0 -1
- package/dist/chunk-ENKXB2BA.js +0 -19
- package/dist/chunk-H3BJOK22.js.map +0 -1
- package/dist/chunk-YY7G4NV3.js.map +0 -1
- package/dist/chunk-ZB6C6CJQ.mjs.map +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const zuiSpeechRecognitionAppearances = {
|
|
2
|
+
default:
|
|
3
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-default-btn-bg,var(--zui-brand,oklch(20.8%_0.042_265.755)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-default-btn-bg-dark,var(--zui-brand-dark,oklch(98.4%_0.003_247.858)))]",
|
|
4
|
+
subtle:
|
|
5
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-subtle-btn-bg,var(--zui-surface-muted,oklch(92.9%_0.013_255.508)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-subtle-btn-bg-dark,var(--zui-surface-muted-dark,oklch(27.9%_0.041_260.031)))]",
|
|
6
|
+
muted:
|
|
7
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-muted-btn-bg,var(--zui-fg-muted,oklch(44.6%_0.043_257.281)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-muted-btn-bg-dark,var(--zui-fg-muted-dark,oklch(86.9%_0.022_252.894)))]",
|
|
8
|
+
primary:
|
|
9
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-primary-btn-bg,var(--zui-fg,oklch(20.8%_0.042_265.755)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-primary-btn-bg-dark,var(--zui-fg-dark,oklch(98.4%_0.003_247.858)))]",
|
|
10
|
+
blue: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-blue-btn-bg,var(--zui-color-blue,#2563eb))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-blue-btn-bg-dark,var(--zui-color-blue-dark,#3b82f6))]",
|
|
11
|
+
cyan: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-cyan-btn-bg,var(--zui-color-cyan,#0891b2))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-cyan-btn-bg-dark,var(--zui-color-cyan-dark,#22d3ee))]",
|
|
12
|
+
green:
|
|
13
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-green-btn-bg,var(--zui-color-green,#16a34a))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-green-btn-bg-dark,var(--zui-color-green-dark,#22c55e))]",
|
|
14
|
+
lime: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-lime-btn-bg,var(--zui-color-lime,#65a30d))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-lime-btn-bg-dark,var(--zui-color-lime-dark,#a3e635))]",
|
|
15
|
+
emerald:
|
|
16
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-emerald-btn-bg,var(--zui-color-emerald,oklch(69.6%_0.17_162.48)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-emerald-btn-bg-dark,var(--zui-color-emerald-dark,oklch(43.2%_0.095_166.913)))]",
|
|
17
|
+
indigo:
|
|
18
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-indigo-btn-bg,var(--zui-color-indigo,oklch(39.8%_0.195_277.366)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-indigo-btn-bg-dark,var(--zui-color-indigo-dark,oklch(51.1%_0.262_276.966)))]",
|
|
19
|
+
purple:
|
|
20
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-purple-btn-bg,var(--zui-color-purple,oklch(43.8%_0.218_303.724)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-purple-btn-bg-dark,var(--zui-color-purple-dark,oklch(55.8%_0.288_302.321)))]",
|
|
21
|
+
pink: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-pink-btn-bg,var(--zui-color-pink,oklch(45.9%_0.187_3.815)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-pink-btn-bg-dark,var(--zui-color-pink-dark,oklch(59.2%_0.249_0.584)))]",
|
|
22
|
+
rose: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-rose-btn-bg,var(--zui-color-rose,oklch(64.5%_0.246_16.439)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-rose-btn-bg-dark,var(--zui-color-rose-dark,oklch(51.4%_0.222_16.935)))]",
|
|
23
|
+
sky: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-sky-btn-bg,var(--zui-color-sky,oklch(68.5%_0.169_237.323)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-sky-btn-bg-dark,var(--zui-color-sky-dark,oklch(50%_0.134_242.749)))]",
|
|
24
|
+
teal: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-teal-btn-bg,var(--zui-color-teal,oklch(70.4%_0.14_182.503)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-teal-btn-bg-dark,var(--zui-color-teal-dark,oklch(51.1%_0.096_186.391)))]",
|
|
25
|
+
yellow:
|
|
26
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-yellow-btn-bg,var(--zui-color-yellow,oklch(79.5%_0.184_86.047)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-yellow-btn-bg-dark,var(--zui-color-yellow-dark,oklch(47.6%_0.114_61.907)))]",
|
|
27
|
+
orange:
|
|
28
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-orange-btn-bg,var(--zui-color-orange,oklch(70.5%_0.213_47.604)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-orange-btn-bg-dark,var(--zui-color-orange-dark,oklch(47%_0.157_37.304)))]",
|
|
29
|
+
red: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-red-btn-bg,var(--zui-color-red,#dc2626))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-red-btn-bg-dark,var(--zui-color-red-dark,#ef4444))]",
|
|
30
|
+
slate:
|
|
31
|
+
"[--zui-recognition-btn-bg:var(--zui-speech-recognition-slate-btn-bg,var(--zui-color-slate,#475569))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-slate-btn-bg-dark,var(--zui-color-slate-dark,#64748b))]",
|
|
32
|
+
gray: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-gray-btn-bg,var(--zui-color-gray,oklch(55.1%_0.027_264.364)))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-gray-btn-bg-dark,var(--zui-color-gray-dark,oklch(55.1%_0.027_264.364)))]",
|
|
33
|
+
zinc: "[--zui-recognition-btn-bg:var(--zui-speech-recognition-zinc-btn-bg,var(--zui-color-zinc,#52525b))] dark:[--zui-recognition-btn-bg:var(--zui-speech-recognition-zinc-btn-bg-dark,var(--zui-color-zinc-dark,#71717a))]",
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export const zuiSpeechRecognitionBase = [
|
|
37
|
+
"inline-flex flex-col gap-2",
|
|
38
|
+
"rounded-[var(--zui-speech-recognition-radius,var(--zui-radius,0.75rem))]",
|
|
39
|
+
"bg-[var(--zui-speech-recognition-bg,var(--zui-surface,#ffffff))] dark:bg-[var(--zui-speech-recognition-bg-dark,var(--zui-surface-dark,#0f172a))]",
|
|
40
|
+
"border border-[var(--zui-speech-recognition-border,var(--zui-border,#e2e8f0))] dark:border-[var(--zui-speech-recognition-border-dark,var(--zui-border-dark,#1e293b))]",
|
|
41
|
+
"p-4",
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
44
|
+
export const zuiSpeechRecognitionStatusBase = [
|
|
45
|
+
"text-[color:var(--zui-speech-recognition-status-fg,var(--zui-fg-muted,oklch(44.6%_0.043_257.281)))] dark:text-[color:var(--zui-speech-recognition-status-fg-dark,var(--zui-fg-muted-dark,oklch(86.9%_0.022_252.894)))]",
|
|
46
|
+
"text-xs",
|
|
47
|
+
"transition-opacity",
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
export const zuiSpeechRecognitionTranscriptBase = [
|
|
51
|
+
"text-[color:var(--zui-speech-recognition-transcript-fg,var(--zui-fg,oklch(20.8%_0.042_265.755)))] dark:text-[color:var(--zui-speech-recognition-transcript-fg-dark,var(--zui-fg-dark,oklch(98.4%_0.003_247.858)))]",
|
|
52
|
+
"text-sm",
|
|
53
|
+
"min-h-[1.25rem]",
|
|
54
|
+
"leading-relaxed",
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
export const zuiSpeechRecognitionSizes = {
|
|
58
|
+
sm: "gap-1.5 p-3",
|
|
59
|
+
md: "gap-2 p-4",
|
|
60
|
+
lg: "gap-3 p-5",
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
export const zuiSpeechRecognitionBtnBase = [
|
|
64
|
+
"inline-flex items-center justify-center rounded-full",
|
|
65
|
+
"size-10",
|
|
66
|
+
"transition-all duration-200",
|
|
67
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
68
|
+
"bg-[var(--zui-recognition-btn-bg,var(--zui-brand,oklch(20.8%_0.042_265.755)))] dark:bg-[var(--zui-recognition-btn-bg,var(--zui-brand-dark,oklch(98.4%_0.003_247.858)))]",
|
|
69
|
+
"text-[color:var(--zui-speech-recognition-btn-fg,var(--zui-bg,#ffffff))] dark:text-[color:var(--zui-speech-recognition-btn-fg-dark,var(--zui-bg-dark,#0f172a))]",
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
export const zuiSpeechRecognitionBtnSizes = {
|
|
73
|
+
sm: "size-8",
|
|
74
|
+
md: "size-10",
|
|
75
|
+
lg: "size-12",
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
export const zuiSpeechRecognitionBtnActive = [
|
|
79
|
+
"ring-2 ring-[var(--zui-speech-recognition-active-ring,var(--zui-status-error,#dc2626))] dark:ring-[var(--zui-speech-recognition-active-ring-dark,var(--zui-status-error-dark,#ef4444))]",
|
|
80
|
+
"ring-offset-2",
|
|
81
|
+
"animate-pulse",
|
|
82
|
+
] as const;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const zuiSpeechSynthesizerAppearances = {
|
|
2
|
+
default:
|
|
3
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-default-btn-bg,var(--zui-brand,oklch(20.8%_0.042_265.755)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-default-btn-bg-dark,var(--zui-brand-dark,oklch(98.4%_0.003_247.858)))]",
|
|
4
|
+
subtle:
|
|
5
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-subtle-btn-bg,var(--zui-surface-muted,oklch(92.9%_0.013_255.508)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-subtle-btn-bg-dark,var(--zui-surface-muted-dark,oklch(27.9%_0.041_260.031)))]",
|
|
6
|
+
muted:
|
|
7
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-muted-btn-bg,var(--zui-fg-muted,oklch(44.6%_0.043_257.281)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-muted-btn-bg-dark,var(--zui-fg-muted-dark,oklch(86.9%_0.022_252.894)))]",
|
|
8
|
+
primary:
|
|
9
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-primary-btn-bg,var(--zui-fg,oklch(20.8%_0.042_265.755)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-primary-btn-bg-dark,var(--zui-fg-dark,oklch(98.4%_0.003_247.858)))]",
|
|
10
|
+
blue: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-blue-btn-bg,var(--zui-color-blue,#2563eb))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-blue-btn-bg-dark,var(--zui-color-blue-dark,#3b82f6))]",
|
|
11
|
+
cyan: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-cyan-btn-bg,var(--zui-color-cyan,#0891b2))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-cyan-btn-bg-dark,var(--zui-color-cyan-dark,#22d3ee))]",
|
|
12
|
+
green:
|
|
13
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-green-btn-bg,var(--zui-color-green,#16a34a))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-green-btn-bg-dark,var(--zui-color-green-dark,#22c55e))]",
|
|
14
|
+
lime: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-lime-btn-bg,var(--zui-color-lime,#65a30d))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-lime-btn-bg-dark,var(--zui-color-lime-dark,#a3e635))]",
|
|
15
|
+
emerald:
|
|
16
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-emerald-btn-bg,var(--zui-color-emerald,oklch(69.6%_0.17_162.48)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-emerald-btn-bg-dark,var(--zui-color-emerald-dark,oklch(43.2%_0.095_166.913)))]",
|
|
17
|
+
indigo:
|
|
18
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-indigo-btn-bg,var(--zui-color-indigo,oklch(39.8%_0.195_277.366)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-indigo-btn-bg-dark,var(--zui-color-indigo-dark,oklch(51.1%_0.262_276.966)))]",
|
|
19
|
+
purple:
|
|
20
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-purple-btn-bg,var(--zui-color-purple,oklch(43.8%_0.218_303.724)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-purple-btn-bg-dark,var(--zui-color-purple-dark,oklch(55.8%_0.288_302.321)))]",
|
|
21
|
+
pink: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-pink-btn-bg,var(--zui-color-pink,oklch(45.9%_0.187_3.815)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-pink-btn-bg-dark,var(--zui-color-pink-dark,oklch(59.2%_0.249_0.584)))]",
|
|
22
|
+
rose: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-rose-btn-bg,var(--zui-color-rose,oklch(64.5%_0.246_16.439)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-rose-btn-bg-dark,var(--zui-color-rose-dark,oklch(51.4%_0.222_16.935)))]",
|
|
23
|
+
sky: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-sky-btn-bg,var(--zui-color-sky,oklch(68.5%_0.169_237.323)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-sky-btn-bg-dark,var(--zui-color-sky-dark,oklch(50%_0.134_242.749)))]",
|
|
24
|
+
teal: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-teal-btn-bg,var(--zui-color-teal,oklch(70.4%_0.14_182.503)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-teal-btn-bg-dark,var(--zui-color-teal-dark,oklch(51.1%_0.096_186.391)))]",
|
|
25
|
+
yellow:
|
|
26
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-yellow-btn-bg,var(--zui-color-yellow,oklch(79.5%_0.184_86.047)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-yellow-btn-bg-dark,var(--zui-color-yellow-dark,oklch(47.6%_0.114_61.907)))]",
|
|
27
|
+
orange:
|
|
28
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-orange-btn-bg,var(--zui-color-orange,oklch(70.5%_0.213_47.604)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-orange-btn-bg-dark,var(--zui-color-orange-dark,oklch(47%_0.157_37.304)))]",
|
|
29
|
+
red: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-red-btn-bg,var(--zui-color-red,#dc2626))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-red-btn-bg-dark,var(--zui-color-red-dark,#ef4444))]",
|
|
30
|
+
slate:
|
|
31
|
+
"[--zui-synth-btn-bg:var(--zui-speech-synthesizer-slate-btn-bg,var(--zui-color-slate,#475569))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-slate-btn-bg-dark,var(--zui-color-slate-dark,#64748b))]",
|
|
32
|
+
gray: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-gray-btn-bg,var(--zui-color-gray,oklch(55.1%_0.027_264.364)))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-gray-btn-bg-dark,var(--zui-color-gray-dark,oklch(55.1%_0.027_264.364)))]",
|
|
33
|
+
zinc: "[--zui-synth-btn-bg:var(--zui-speech-synthesizer-zinc-btn-bg,var(--zui-color-zinc,#52525b))] dark:[--zui-synth-btn-bg:var(--zui-speech-synthesizer-zinc-btn-bg-dark,var(--zui-color-zinc-dark,#71717a))]",
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export const zuiSpeechSynthesizerBase = [
|
|
37
|
+
"inline-flex flex-col gap-3",
|
|
38
|
+
"rounded-[var(--zui-speech-synthesizer-radius,var(--zui-radius,0.75rem))]",
|
|
39
|
+
"bg-[var(--zui-speech-synthesizer-bg,var(--zui-surface,#ffffff))] dark:bg-[var(--zui-speech-synthesizer-bg-dark,var(--zui-surface-dark,#0f172a))]",
|
|
40
|
+
"border border-[var(--zui-speech-synthesizer-border,var(--zui-border,#e2e8f0))] dark:border-[var(--zui-speech-synthesizer-border-dark,var(--zui-border-dark,#1e293b))]",
|
|
41
|
+
"p-4",
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
44
|
+
export const zuiSpeechSynthesizerSizes = {
|
|
45
|
+
sm: "gap-2 p-3",
|
|
46
|
+
md: "gap-3 p-4",
|
|
47
|
+
lg: "gap-4 p-5",
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
export const zuiSpeechSynthesizerBtnBase = [
|
|
51
|
+
"inline-flex items-center justify-center rounded-full",
|
|
52
|
+
"size-10",
|
|
53
|
+
"transition-all duration-200",
|
|
54
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
55
|
+
"bg-[var(--zui-synth-btn-bg,var(--zui-brand,oklch(20.8%_0.042_265.755)))] dark:bg-[var(--zui-synth-btn-bg,var(--zui-brand-dark,oklch(98.4%_0.003_247.858)))]",
|
|
56
|
+
"text-[color:var(--zui-speech-synthesizer-btn-fg,var(--zui-bg,#ffffff))] dark:text-[color:var(--zui-speech-synthesizer-btn-fg-dark,var(--zui-bg-dark,#0f172a))]",
|
|
57
|
+
] as const;
|
|
58
|
+
|
|
59
|
+
export const zuiSpeechSynthesizerBtnSizes = {
|
|
60
|
+
sm: "size-8",
|
|
61
|
+
md: "size-10",
|
|
62
|
+
lg: "size-12",
|
|
63
|
+
} as const;
|
|
64
|
+
|
|
65
|
+
export const zuiSpeechSynthesizerBtnActive = [
|
|
66
|
+
"ring-2 ring-[var(--zui-speech-synthesizer-active-ring,var(--zui-color-green,#16a34a))] dark:ring-[var(--zui-speech-synthesizer-active-ring-dark,var(--zui-color-green-dark,#22c55e))]",
|
|
67
|
+
"ring-offset-2",
|
|
68
|
+
"animate-pulse",
|
|
69
|
+
] as const;
|
|
70
|
+
|
|
71
|
+
export const zuiSpeechSynthesizerTextBase = [
|
|
72
|
+
"text-[color:var(--zui-speech-synthesizer-text-fg,var(--zui-fg,oklch(20.8%_0.042_265.755)))] dark:text-[color:var(--zui-speech-synthesizer-text-fg-dark,var(--zui-fg-dark,oklch(98.4%_0.003_247.858)))]",
|
|
73
|
+
"text-sm",
|
|
74
|
+
"leading-relaxed",
|
|
75
|
+
] as const;
|
|
76
|
+
|
|
77
|
+
export const zuiSpeechSynthesizerControlsBase = [
|
|
78
|
+
"inline-flex items-center gap-2",
|
|
79
|
+
] as const;
|
|
80
|
+
|
|
81
|
+
export const zuiSpeechSynthesizerProgressBase = [
|
|
82
|
+
"relative w-full h-1 overflow-hidden rounded-full",
|
|
83
|
+
"bg-[var(--zui-speech-synthesizer-track-bg,var(--zui-surface-muted,#0000001a))] dark:bg-[var(--zui-speech-synthesizer-track-bg-dark,var(--zui-surface-muted-dark,#ffffff1a))]",
|
|
84
|
+
] as const;
|
|
85
|
+
|
|
86
|
+
export const zuiSpeechSynthesizerProgressBarBase = [
|
|
87
|
+
"h-full rounded-full",
|
|
88
|
+
"bg-[var(--zui-synth-btn-bg,var(--zui-brand,oklch(20.8%_0.042_265.755)))] dark:bg-[var(--zui-synth-btn-bg,var(--zui-brand-dark,oklch(98.4%_0.003_247.858)))]",
|
|
89
|
+
"transition-all duration-300",
|
|
90
|
+
] as const;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Transition, Variants } from "framer-motion";
|
|
2
|
+
|
|
3
|
+
export type QrCodeAnimation =
|
|
4
|
+
| "none"
|
|
5
|
+
| "fade-in"
|
|
6
|
+
| "zoom-in"
|
|
7
|
+
| "slide-up"
|
|
8
|
+
| "rotate-in";
|
|
9
|
+
|
|
10
|
+
export type QrCodeAnimationPresets = Record<
|
|
11
|
+
QrCodeAnimation,
|
|
12
|
+
{ transition: Transition; variants: Variants }
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
export const qrCodeAnimationPresets: QrCodeAnimationPresets = {
|
|
16
|
+
none: {
|
|
17
|
+
transition: { duration: 0 },
|
|
18
|
+
variants: {
|
|
19
|
+
initial: { opacity: 1 },
|
|
20
|
+
animate: { opacity: 1 },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
"fade-in": {
|
|
24
|
+
transition: { duration: 0.3, ease: "easeOut" },
|
|
25
|
+
variants: {
|
|
26
|
+
initial: { opacity: 0 },
|
|
27
|
+
animate: { opacity: 1 },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
"zoom-in": {
|
|
31
|
+
transition: { type: "spring", stiffness: 300, damping: 24 },
|
|
32
|
+
variants: {
|
|
33
|
+
initial: { opacity: 0, scale: 0.85 },
|
|
34
|
+
animate: { opacity: 1, scale: 1 },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
"slide-up": {
|
|
38
|
+
transition: { type: "spring", stiffness: 280, damping: 22 },
|
|
39
|
+
variants: {
|
|
40
|
+
initial: { opacity: 0, y: 20 },
|
|
41
|
+
animate: { opacity: 1, y: 0 },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
"rotate-in": {
|
|
45
|
+
transition: { type: "spring", stiffness: 200, damping: 18 },
|
|
46
|
+
variants: {
|
|
47
|
+
initial: { opacity: 0, rotate: -90, scale: 0.8 },
|
|
48
|
+
animate: { opacity: 1, rotate: 0, scale: 1 },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { motion } from "framer-motion";
|
|
5
|
+
import QRCode from "qrcode";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../../lib/utils";
|
|
8
|
+
import { qrCodeAnimationPresets } from "./animations";
|
|
9
|
+
import type { QrCodeAnimatedProps } from "./types";
|
|
10
|
+
import { QrCodeBase } from "../qr-code-base";
|
|
11
|
+
|
|
12
|
+
export function QrCodeAnimated({
|
|
13
|
+
animation = "none",
|
|
14
|
+
value,
|
|
15
|
+
canvasSize = 200,
|
|
16
|
+
level = "M",
|
|
17
|
+
bgColor = "#ffffff",
|
|
18
|
+
fgColor = "#000000",
|
|
19
|
+
margin = 2,
|
|
20
|
+
caption,
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}: QrCodeAnimatedProps) {
|
|
24
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const canvas = canvasRef.current;
|
|
28
|
+
if (!canvas || animation === "none") return;
|
|
29
|
+
|
|
30
|
+
if (!value) {
|
|
31
|
+
const ctx = canvas.getContext("2d");
|
|
32
|
+
if (ctx) {
|
|
33
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
QRCode.toCanvas(
|
|
39
|
+
canvas,
|
|
40
|
+
value,
|
|
41
|
+
{
|
|
42
|
+
width: canvasSize,
|
|
43
|
+
margin,
|
|
44
|
+
color: {
|
|
45
|
+
dark: fgColor,
|
|
46
|
+
light: bgColor,
|
|
47
|
+
},
|
|
48
|
+
errorCorrectionLevel: level,
|
|
49
|
+
},
|
|
50
|
+
(error) => {
|
|
51
|
+
if (error) {
|
|
52
|
+
console.error("QR Code generation error:", error);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
}, [value, canvasSize, level, bgColor, fgColor, margin, animation]);
|
|
57
|
+
|
|
58
|
+
if (animation === "none") {
|
|
59
|
+
return (
|
|
60
|
+
<QrCodeBase
|
|
61
|
+
value={value}
|
|
62
|
+
canvasSize={canvasSize}
|
|
63
|
+
level={level}
|
|
64
|
+
bgColor={bgColor}
|
|
65
|
+
fgColor={fgColor}
|
|
66
|
+
margin={margin}
|
|
67
|
+
caption={caption}
|
|
68
|
+
className={className}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const preset = qrCodeAnimationPresets[animation];
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<motion.div
|
|
78
|
+
data-slot="qr-code"
|
|
79
|
+
className={cn(
|
|
80
|
+
"inline-flex flex-col items-center gap-3 rounded-2xl border p-6 border-[color:var(--zui-qr-code-border,var(--zui-border,#0000001a))] dark:border-[color:var(--zui-qr-code-border-dark,var(--zui-border-dark,#ffffff1a))] bg-[var(--zui-qr-code-bg,var(--zui-surface,oklch(98.4%_0.003_247.858)))] dark:bg-[var(--zui-qr-code-bg-dark,var(--zui-surface-dark,oklch(12.9%_0.042_264.695)))]",
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
variants={preset.variants}
|
|
84
|
+
initial="initial"
|
|
85
|
+
animate="animate"
|
|
86
|
+
transition={preset.transition}
|
|
87
|
+
{...(props as Record<string, unknown>)}
|
|
88
|
+
>
|
|
89
|
+
<div className="overflow-hidden rounded-xl bg-[var(--zui-qr-code-canvas-bg,var(--zui-surface-muted,oklch(92.9%_0.013_255.508)))] dark:bg-[var(--zui-qr-code-canvas-bg-dark,var(--zui-surface-muted-dark,oklch(27.9%_0.041_260.031)))]">
|
|
90
|
+
<canvas
|
|
91
|
+
ref={canvasRef}
|
|
92
|
+
width={canvasSize}
|
|
93
|
+
height={canvasSize}
|
|
94
|
+
data-slot="qr-code-canvas"
|
|
95
|
+
className="block h-auto max-w-full"
|
|
96
|
+
aria-label={`QR code for ${value}`}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
{caption ? (
|
|
100
|
+
<span
|
|
101
|
+
data-slot="qr-code-caption"
|
|
102
|
+
className="text-xs text-center text-[color:var(--zui-qr-code-caption-fg,var(--zui-fg-muted,oklch(55.2%_0.046_257.417)))] dark:text-[color:var(--zui-qr-code-caption-fg-dark,var(--zui-fg-muted-dark,oklch(70.8%_0.015_256.243)))] max-w-full truncate px-2"
|
|
103
|
+
>
|
|
104
|
+
{caption}
|
|
105
|
+
</span>
|
|
106
|
+
) : null}
|
|
107
|
+
</motion.div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
QrCodeAnimated.displayName = "QrCodeAnimated";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Ref } from "react";
|
|
2
|
+
import type { QrCodeBaseProps } from "../types";
|
|
3
|
+
import type { QrCodeAnimation } from "./animations";
|
|
4
|
+
|
|
5
|
+
export type { QrCodeAnimation };
|
|
6
|
+
|
|
7
|
+
export type QrCodeAnimatedProps = QrCodeBaseProps & {
|
|
8
|
+
animation?: QrCodeAnimation;
|
|
9
|
+
ref?: Ref<HTMLDivElement>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export { QrCode } from "./qr-code";
|
|
4
|
+
export type { QrCodeBaseProps, QrCodeLevel, QrCodeProps } from "./types";
|
|
5
|
+
export { QR_CODE_LEVEL_LABELS } from "./types";
|
|
6
|
+
export {
|
|
7
|
+
qrCodeCanvasWrapperVariants,
|
|
8
|
+
qrCodeCaptionVariants,
|
|
9
|
+
qrCodeVariants,
|
|
10
|
+
} from "./variants";
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import QRCode from "qrcode";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
|
|
8
|
+
import type { QrCodeBaseProps } from "./types";
|
|
9
|
+
import {
|
|
10
|
+
qrCodeCanvasWrapperVariants,
|
|
11
|
+
qrCodeCaptionVariants,
|
|
12
|
+
qrCodeVariants,
|
|
13
|
+
} from "./variants";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* QrCodeBase renders a QR code onto a `<canvas>` element using the `qrcode`
|
|
17
|
+
* npm package. It is the static (non-animated) implementation of the public
|
|
18
|
+
* `QrCode` component.
|
|
19
|
+
*
|
|
20
|
+
* --- Props ---
|
|
21
|
+
*
|
|
22
|
+
* - `value` — The string to encode. When empty/falsy the canvas is cleared.
|
|
23
|
+
* - `canvasSize` — Pixel dimensions of the canvas (default: 200). Both width
|
|
24
|
+
* and height are set to this value, producing a square QR code.
|
|
25
|
+
* - `level` — Error correction level (`"L"` | `"M"` | `"Q"` | `"H"`). Higher
|
|
26
|
+
* levels survive more visual damage but hold less data.
|
|
27
|
+
* - `bgColor` / `fgColor` — Background and foreground colours passed to the
|
|
28
|
+
* underlying `QRCode.toCanvas` options.
|
|
29
|
+
* - `margin` — Quiet-zone width in QR modules (default: 2). Passed directly
|
|
30
|
+
* to the `qrcode` library.
|
|
31
|
+
* - `caption` — Optional label rendered below the QR code.
|
|
32
|
+
* - `ref` — React 19 ref forwarded to the root `<div>` element.
|
|
33
|
+
* - `className` / `...rest` — Spread onto the root `<div>` for layout or
|
|
34
|
+
* style overrides.
|
|
35
|
+
*
|
|
36
|
+
* --- Rendering lifecycle ---
|
|
37
|
+
*
|
|
38
|
+
* A `useEffect` watches `[value, canvasSize, level, bgColor, fgColor, margin]`.
|
|
39
|
+
* On every change it:
|
|
40
|
+
* 1. Grabs the canvas element from the ref.
|
|
41
|
+
* 2. If `value` is empty, clears the canvas with `ctx.clearRect` so a stale
|
|
42
|
+
* QR code doesn't persist.
|
|
43
|
+
* 3. Otherwise calls `QRCode.toCanvas()` which draws the QR pattern
|
|
44
|
+
* asynchronously (via callback) onto the canvas element.
|
|
45
|
+
*
|
|
46
|
+
* The outer `<div>` uses design-system variants (`qrCodeVariants`) for
|
|
47
|
+
* consistent theming and carries `data-slot="qr-code"` for scoped styling and
|
|
48
|
+
* testing.
|
|
49
|
+
*
|
|
50
|
+
* --- Accessibility ---
|
|
51
|
+
*
|
|
52
|
+
* The `<canvas>` has an `aria-label` describing it as a "QR code for {value}".
|
|
53
|
+
* Screen readers will announce the purpose, though the raw value is exposed
|
|
54
|
+
* (consider truncating for secrets).
|
|
55
|
+
*/
|
|
56
|
+
export function QrCodeBase({
|
|
57
|
+
value,
|
|
58
|
+
canvasSize = 200,
|
|
59
|
+
level = "M",
|
|
60
|
+
bgColor = "#ffffff",
|
|
61
|
+
fgColor = "#000000",
|
|
62
|
+
margin = 2,
|
|
63
|
+
caption,
|
|
64
|
+
className,
|
|
65
|
+
ref,
|
|
66
|
+
...rest
|
|
67
|
+
}: QrCodeBaseProps) {
|
|
68
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Draw the QR pattern whenever input props change.
|
|
72
|
+
*
|
|
73
|
+
* The conditional `if (!value)` branch handles the "clear" case: when the
|
|
74
|
+
* user deletes the input text, we clear the canvas so a stale QR code isn't
|
|
75
|
+
* displayed. Without this the old QR pattern persists because
|
|
76
|
+
* `QRCode.toCanvas` is never called (it returns early for empty values).
|
|
77
|
+
*/
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const canvas = canvasRef.current;
|
|
80
|
+
if (!canvas) return;
|
|
81
|
+
|
|
82
|
+
if (!value) {
|
|
83
|
+
const ctx = canvas.getContext("2d");
|
|
84
|
+
if (ctx) {
|
|
85
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
QRCode.toCanvas(
|
|
91
|
+
canvas,
|
|
92
|
+
value,
|
|
93
|
+
{
|
|
94
|
+
width: canvasSize,
|
|
95
|
+
margin,
|
|
96
|
+
color: {
|
|
97
|
+
dark: fgColor,
|
|
98
|
+
light: bgColor,
|
|
99
|
+
},
|
|
100
|
+
errorCorrectionLevel: level,
|
|
101
|
+
},
|
|
102
|
+
(error) => {
|
|
103
|
+
if (error) {
|
|
104
|
+
console.error("QR Code generation error:", error);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
}, [value, canvasSize, level, bgColor, fgColor, margin]);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The root element is a `<div>` (not the `<canvas>`) so consumers can apply
|
|
112
|
+
* layout classes, the caption sits outside the canvas, and the component
|
|
113
|
+
* plays well with flex/grid containers.
|
|
114
|
+
*
|
|
115
|
+
* Nested structure:
|
|
116
|
+
* <div data-slot="qr-code"> ← root (ref, className, ...rest)
|
|
117
|
+
* <div> ← canvas wrapper (theme rounding/overflow)
|
|
118
|
+
* <canvas data-slot="qr-code-canvas" />
|
|
119
|
+
* </div>
|
|
120
|
+
* {caption && <span data-slot="qr-code-caption" />}
|
|
121
|
+
* </div>
|
|
122
|
+
*/
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
ref={ref}
|
|
126
|
+
data-slot="qr-code"
|
|
127
|
+
className={cn(qrCodeVariants(), className)}
|
|
128
|
+
{...rest}
|
|
129
|
+
>
|
|
130
|
+
<div className={qrCodeCanvasWrapperVariants()}>
|
|
131
|
+
<canvas
|
|
132
|
+
ref={canvasRef}
|
|
133
|
+
width={canvasSize}
|
|
134
|
+
height={canvasSize}
|
|
135
|
+
data-slot="qr-code-canvas"
|
|
136
|
+
className="block h-auto max-w-full"
|
|
137
|
+
aria-label={`QR code for ${value}`}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
{caption ? (
|
|
141
|
+
<span data-slot="qr-code-caption" className={qrCodeCaptionVariants()}>
|
|
142
|
+
{caption}
|
|
143
|
+
</span>
|
|
144
|
+
) : null}
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
QrCodeBase.displayName = "QrCode";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRef } from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { QrCode } from "./qr-code";
|
|
6
|
+
|
|
7
|
+
vi.mock("qrcode", () => ({
|
|
8
|
+
default: {
|
|
9
|
+
toCanvas: vi.fn(
|
|
10
|
+
(
|
|
11
|
+
_canvas: HTMLCanvasElement,
|
|
12
|
+
_value: string,
|
|
13
|
+
_opts: unknown,
|
|
14
|
+
cb?: (err?: Error) => void,
|
|
15
|
+
) => {
|
|
16
|
+
cb?.();
|
|
17
|
+
},
|
|
18
|
+
),
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe("QrCode", () => {
|
|
23
|
+
it("should expose displayName", () => {
|
|
24
|
+
expect(QrCode.displayName).toBe("QrCode");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should stamp data-slot", () => {
|
|
28
|
+
render(<QrCode value="https://example.com" />);
|
|
29
|
+
const root = document.querySelector('[data-slot="qr-code"]');
|
|
30
|
+
expect(root).toBeTruthy();
|
|
31
|
+
expect(root?.getAttribute("data-slot")).toBe("qr-code");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should render canvas element", () => {
|
|
35
|
+
render(<QrCode value="https://example.com" />);
|
|
36
|
+
const canvas = document.querySelector('[data-slot="qr-code-canvas"]');
|
|
37
|
+
expect(canvas).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should render with caption", () => {
|
|
41
|
+
render(<QrCode value="test" caption="Scan me" />);
|
|
42
|
+
expect(screen.getByText("Scan me")).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should forward ref", () => {
|
|
46
|
+
const ref = createRef<HTMLDivElement>();
|
|
47
|
+
render(<QrCode value="test" ref={ref} />);
|
|
48
|
+
expect(ref.current?.getAttribute("data-slot")).toBe("qr-code");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should apply custom className", () => {
|
|
52
|
+
const { container } = render(
|
|
53
|
+
<QrCode value="test" className="custom-class" />,
|
|
54
|
+
);
|
|
55
|
+
const root = container.querySelector('[data-slot="qr-code"]');
|
|
56
|
+
expect(root?.className).toMatch(/custom-class/);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type QrCodeLevel = "L" | "M" | "Q" | "H";
|
|
4
|
+
|
|
5
|
+
export interface QrCodeBaseProps extends ComponentPropsWithRef<"div"> {
|
|
6
|
+
value: string;
|
|
7
|
+
canvasSize?: number;
|
|
8
|
+
level?: QrCodeLevel;
|
|
9
|
+
bgColor?: string;
|
|
10
|
+
fgColor?: string;
|
|
11
|
+
margin?: number;
|
|
12
|
+
caption?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type QrCodeProps = QrCodeBaseProps;
|
|
16
|
+
|
|
17
|
+
export const QR_CODE_LEVEL_LABELS: Record<QrCodeLevel, string> = {
|
|
18
|
+
L: "Low (7%)",
|
|
19
|
+
M: "Medium (15%)",
|
|
20
|
+
Q: "Quartile (25%)",
|
|
21
|
+
H: "High (30%)",
|
|
22
|
+
} as const;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
zuiQrCodeBase,
|
|
5
|
+
zuiQrCodeCanvasWrapper,
|
|
6
|
+
zuiQrCodeCaptionBase,
|
|
7
|
+
} from "../../design-system/qr-code";
|
|
8
|
+
|
|
9
|
+
export const qrCodeVariants = cva(zuiQrCodeBase);
|
|
10
|
+
export const qrCodeCanvasWrapperVariants = cva(zuiQrCodeCanvasWrapper);
|
|
11
|
+
export const qrCodeCaptionVariants = cva(zuiQrCodeCaptionBase);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export { QrScanner } from "./qr-scanner";
|
|
4
|
+
export type {
|
|
5
|
+
QrScannerBaseProps,
|
|
6
|
+
QrScannerProps,
|
|
7
|
+
QrScannerRef,
|
|
8
|
+
QrScannerVariantProps,
|
|
9
|
+
} from "./types";
|
|
10
|
+
export {
|
|
11
|
+
qrScannerFallbackVariants,
|
|
12
|
+
qrScannerOverlayVariants,
|
|
13
|
+
qrScannerStatusVariants,
|
|
14
|
+
qrScannerVariants,
|
|
15
|
+
qrScannerVideoVariants,
|
|
16
|
+
qrScannerViewfinderVariants,
|
|
17
|
+
} from "./variants";
|