@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerodev/react-ui",
3
- "version": "0.0.7",
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
- {children}
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 {