@zerodev/react-ui 0.0.7 → 0.0.8
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/dist/_types/components/BottomSheet/index.d.ts.map +1 -1
- package/dist/_types/components/QrCode/index.d.ts +27 -0
- package/dist/_types/components/QrCode/index.d.ts.map +1 -0
- package/dist/_types/index.d.ts +1 -0
- package/dist/_types/index.d.ts.map +1 -1
- package/dist/index.cjs +18 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2731 -2130
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/components/BottomSheet/index.tsx +9 -1
- package/src/components/QrCode/index.tsx +163 -0
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerodev/react-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "React UI primitives for ZeroDev",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -67,7 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"clsx": "^2.1.1",
|
|
70
|
-
"tailwind-merge": "^3.5.0"
|
|
70
|
+
"tailwind-merge": "^3.5.0",
|
|
71
|
+
"uqr": "^0.1.2"
|
|
71
72
|
},
|
|
72
73
|
"scripts": {
|
|
73
74
|
"build": "pnpm run clean && pnpm run build:css && pnpm run build:js && pnpm run build:types",
|
|
@@ -50,7 +50,15 @@ export function BottomSheetContent({
|
|
|
50
50
|
className,
|
|
51
51
|
)}
|
|
52
52
|
>
|
|
53
|
-
|
|
53
|
+
<div
|
|
54
|
+
aria-hidden
|
|
55
|
+
className="zd:absolute zd:inset-0 zd:z-0 zd:pointer-events-none"
|
|
56
|
+
style={{
|
|
57
|
+
background:
|
|
58
|
+
'radial-gradient(ellipse 78% 65% at 5% 105%, rgba(69,171,251,0.35) 0%, rgba(69,171,251,0) 72%), radial-gradient(ellipse 82% 65% at 100% 105%, rgba(250,200,172,0.65) 0%, rgba(250,200,172,0) 72%), radial-gradient(ellipse 58% 52% at 100% 100%, rgba(242,123,62,0.35) 0%, rgba(242,123,62,0) 72%)',
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
<div className="zd:relative zd:z-10">{children}</div>
|
|
54
62
|
</Dialog.Content>
|
|
55
63
|
</Dialog.Portal>
|
|
56
64
|
)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { encode } from 'uqr'
|
|
2
|
+
|
|
3
|
+
export interface QrCodeProps {
|
|
4
|
+
/** Data to encode. */
|
|
5
|
+
value: string
|
|
6
|
+
/** Pixel size of the rendered SVG square. */
|
|
7
|
+
size: number
|
|
8
|
+
/** Error correction level. Defaults to `'M'`. */
|
|
9
|
+
errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'
|
|
10
|
+
/** Corner radius (in modules, scaled to px internally) for finder patterns. */
|
|
11
|
+
eyeRadius?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const FINDER_SIZE = 7 // every QR has three 7×7 finder patterns
|
|
15
|
+
const MODULE_COLOR = '#000'
|
|
16
|
+
const BG_COLOR = '#fff'
|
|
17
|
+
/** Vertical shrink applied to data-module pills so rows read as distinct
|
|
18
|
+
* without introducing white slivers wide enough to look like transitions
|
|
19
|
+
* to a scanner. Kept small (5%) so total black area stays close to spec. */
|
|
20
|
+
const PILL_PAD_RATIO = 0.05
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Custom QR renderer that draws runs of horizontally-adjacent data modules as
|
|
24
|
+
* a single rounded pill. Finder patterns stay as sharp concentric squares
|
|
25
|
+
* (rounding them breaks the 1:1:3:1:1 corner ratio scanners use to locate the
|
|
26
|
+
* code). Isolated data modules render as pill-ish rects — visually close to
|
|
27
|
+
* circles at very small `PILL_PAD_RATIO`, still square-adjacent for scanners.
|
|
28
|
+
*
|
|
29
|
+
* Default `errorCorrectionLevel` is `'H'` (30% recovery) so the decorative
|
|
30
|
+
* rounding + shrinkage has plenty of headroom before scans fail.
|
|
31
|
+
*
|
|
32
|
+
* Quiet zone (spec: ≥4 modules of white around the code) is expected to be
|
|
33
|
+
* provided by the caller — e.g. via a white-background padded wrapper. This
|
|
34
|
+
* component fills the given `size` with the QR data area itself so the
|
|
35
|
+
* consumer controls the visual footprint.
|
|
36
|
+
*/
|
|
37
|
+
export function QrCode({
|
|
38
|
+
value,
|
|
39
|
+
size,
|
|
40
|
+
errorCorrectionLevel = 'H',
|
|
41
|
+
eyeRadius = 0,
|
|
42
|
+
}: QrCodeProps) {
|
|
43
|
+
// `uqr` returns a 2D boolean matrix (`data[row][col]`) and the module count
|
|
44
|
+
// per side. Same information as `qrcode`, just shaped differently — the
|
|
45
|
+
// pill/finder rendering below is unchanged.
|
|
46
|
+
const qr = encode(value, { ecc: errorCorrectionLevel, border: 0 })
|
|
47
|
+
const matrix = qr.data
|
|
48
|
+
const moduleCount = qr.size
|
|
49
|
+
const cellSize = size / moduleCount
|
|
50
|
+
const eyeRadiusPx = eyeRadius * cellSize
|
|
51
|
+
|
|
52
|
+
const finders = [
|
|
53
|
+
{ row: 0, col: 0 },
|
|
54
|
+
{ row: 0, col: moduleCount - FINDER_SIZE },
|
|
55
|
+
{ row: moduleCount - FINDER_SIZE, col: 0 },
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
const inFinder = (row: number, col: number) =>
|
|
59
|
+
finders.some(
|
|
60
|
+
(f) =>
|
|
61
|
+
row >= f.row &&
|
|
62
|
+
row < f.row + FINDER_SIZE &&
|
|
63
|
+
col >= f.col &&
|
|
64
|
+
col < f.col + FINDER_SIZE,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// Collect maximal horizontal runs of `on` modules, skipping finder regions.
|
|
68
|
+
const runs: { row: number; col: number; length: number }[] = []
|
|
69
|
+
for (let row = 0; row < moduleCount; row++) {
|
|
70
|
+
let col = 0
|
|
71
|
+
while (col < moduleCount) {
|
|
72
|
+
if (inFinder(row, col)) {
|
|
73
|
+
col++
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (matrix[row]?.[col]) {
|
|
77
|
+
let length = 1
|
|
78
|
+
while (
|
|
79
|
+
col + length < moduleCount &&
|
|
80
|
+
!inFinder(row, col + length) &&
|
|
81
|
+
matrix[row]?.[col + length]
|
|
82
|
+
) {
|
|
83
|
+
length++
|
|
84
|
+
}
|
|
85
|
+
runs.push({ row, col, length })
|
|
86
|
+
col += length
|
|
87
|
+
} else {
|
|
88
|
+
col++
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<svg
|
|
95
|
+
width={size}
|
|
96
|
+
height={size}
|
|
97
|
+
viewBox={`0 0 ${size} ${size}`}
|
|
98
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
99
|
+
role="img"
|
|
100
|
+
aria-label={`QR code for ${value}`}
|
|
101
|
+
>
|
|
102
|
+
<rect width={size} height={size} fill={BG_COLOR} />
|
|
103
|
+
{runs.map(({ row, col, length }) => {
|
|
104
|
+
// Shrink each pill vertically so rows read as distinct without
|
|
105
|
+
// creating scanner-confusing white gaps mid-run. Isolated modules
|
|
106
|
+
// (length === 1) also shrink horizontally so they render close to a
|
|
107
|
+
// circle instead of an oval.
|
|
108
|
+
const pad = cellSize * PILL_PAD_RATIO
|
|
109
|
+
const pillHeight = cellSize - 2 * pad
|
|
110
|
+
const isSingle = length === 1
|
|
111
|
+
return (
|
|
112
|
+
<rect
|
|
113
|
+
key={`r${row}-${col}`}
|
|
114
|
+
x={col * cellSize + (isSingle ? pad : 0)}
|
|
115
|
+
y={row * cellSize + pad}
|
|
116
|
+
width={length * cellSize - (isSingle ? 2 * pad : 0)}
|
|
117
|
+
height={pillHeight}
|
|
118
|
+
rx={pillHeight / 2}
|
|
119
|
+
ry={pillHeight / 2}
|
|
120
|
+
fill={MODULE_COLOR}
|
|
121
|
+
/>
|
|
122
|
+
)
|
|
123
|
+
})}
|
|
124
|
+
{finders.map(({ row, col }) => {
|
|
125
|
+
const x = col * cellSize
|
|
126
|
+
const y = row * cellSize
|
|
127
|
+
const outerSize = FINDER_SIZE * cellSize
|
|
128
|
+
const innerSize = 3 * cellSize
|
|
129
|
+
return (
|
|
130
|
+
<g key={`f${row}-${col}`}>
|
|
131
|
+
<rect
|
|
132
|
+
x={x}
|
|
133
|
+
y={y}
|
|
134
|
+
width={outerSize}
|
|
135
|
+
height={outerSize}
|
|
136
|
+
rx={eyeRadiusPx}
|
|
137
|
+
ry={eyeRadiusPx}
|
|
138
|
+
fill={MODULE_COLOR}
|
|
139
|
+
/>
|
|
140
|
+
<rect
|
|
141
|
+
x={x + cellSize}
|
|
142
|
+
y={y + cellSize}
|
|
143
|
+
width={outerSize - 2 * cellSize}
|
|
144
|
+
height={outerSize - 2 * cellSize}
|
|
145
|
+
rx={Math.max(eyeRadiusPx - cellSize, 0)}
|
|
146
|
+
ry={Math.max(eyeRadiusPx - cellSize, 0)}
|
|
147
|
+
fill={BG_COLOR}
|
|
148
|
+
/>
|
|
149
|
+
<rect
|
|
150
|
+
x={x + 2 * cellSize}
|
|
151
|
+
y={y + 2 * cellSize}
|
|
152
|
+
width={innerSize}
|
|
153
|
+
height={innerSize}
|
|
154
|
+
rx={Math.max(eyeRadiusPx - 2 * cellSize, 0)}
|
|
155
|
+
ry={Math.max(eyeRadiusPx - 2 * cellSize, 0)}
|
|
156
|
+
fill={MODULE_COLOR}
|
|
157
|
+
/>
|
|
158
|
+
</g>
|
|
159
|
+
)
|
|
160
|
+
})}
|
|
161
|
+
</svg>
|
|
162
|
+
)
|
|
163
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export {
|
|
|
55
55
|
type ProgressStepProps,
|
|
56
56
|
type ProgressStepStatus,
|
|
57
57
|
} from './components/ProgressStep'
|
|
58
|
+
export { QrCode, type QrCodeProps } from './components/QrCode'
|
|
58
59
|
export { Screen } from './components/Screen'
|
|
59
60
|
export { Section, type SectionProps } from './components/Section'
|
|
60
61
|
export {
|