Skip to content

Commit 3bf5f08

Browse files
authored
feat: add boostLevel prop and support value segmentation (#374)
1 parent 0b17e8c commit 3bf5f08

File tree

5 files changed

+130
-12
lines changed

5 files changed

+130
-12
lines changed

Diff for: README.md

+21-5
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ Below is a condensed type definition of the props `QRCodeSVG` and `QRCodeCanvas`
4747
```ts
4848
type QRProps = {
4949
/**
50-
* The value to encode into the QR Code.
50+
* The value to encode into the QR Code. An array of strings can be passed in
51+
* to represent multiple segments to further optimize the QR Code.
5152
*/
52-
value: string;
53+
value: string | string[];
5354
/**
5455
* The size, in pixels, to render the QR Code.
5556
* @defaultValue 128
@@ -99,6 +100,13 @@ type QRProps = {
99100
* @defaultValue 1
100101
*/
101102
minVersion?: number;
103+
/**
104+
* If enabled, the Error Correction Level of the result may be higher than
105+
* the specified Error Correction Level option if it can be done without
106+
* increasing the version.
107+
* @defaultValue true
108+
*/
109+
boostLevel?: boolean;
102110
/**
103111
* The settings for the embedded image.
104112
*/
@@ -153,9 +161,9 @@ type QRProps = {
153161

154162
The value to encode into the QR Code. See [Encoding Mode](#encoding-mode) for additional details.
155163

156-
| Type | Default Value |
157-
| -------- | ------------- |
158-
| `string` | |
164+
| Type | Default Value |
165+
| ------------------- | ------------- |
166+
| `string \| string[]` ||
159167

160168
### `size`
161169

@@ -231,6 +239,14 @@ The minimum version used when encoding the QR Code. Valid values are 1-40 with h
231239
| -------- | ------------- |
232240
| `number` | `1` |
233241

242+
### `boostLevel`
243+
244+
If enabled, the Error Correction Level of the result may be higher than the specified Error Correction Level option if it can be done without increasing the version.
245+
246+
| Type | Default Value |
247+
| --------- | ------------- |
248+
| `boolean` | `true` |
249+
234250
### `imageSettings`
235251

236252
Used to specify the details for an embedded image, often used to embed a logo.

Diff for: examples/full.tsx

+18-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ function FullDemo() {
1212
const [fgColor, setFgColor] = useState('#000000');
1313
const [bgColor, setBgColor] = useState('#ffffff');
1414
const [level, setLevel] = useState<ErrorCorrectionLevel>('L');
15+
const [boostLevel, setBoostLevel] = useState<boolean>(true);
1516
const [minVersion, setMinVersion] = useState(1);
1617
const [marginSize, setMarginSize] = useState(0);
1718
const [title, setTitle] = useState('Title for my QR Code');
@@ -45,6 +46,10 @@ function FullDemo() {
4546
? `minVersion={${minVersion}}
4647
`
4748
: '';
49+
const boostLevelCode = !boostLevel
50+
? `boostLevel={${boostLevel}}
51+
`
52+
: '';
4853
return `import {${componentName}} from 'qrcode.react';
4954
<${componentName}
5055
value={"${value}"}
@@ -53,7 +58,7 @@ function FullDemo() {
5358
bgColor={"${bgColor}"}
5459
fgColor={"${fgColor}"}
5560
level={"${level}"}
56-
${minVersionCode}marginSize={${marginSize}}${imageSettingsCode}
61+
${minVersionCode}${boostLevelCode}marginSize={${marginSize}}${imageSettingsCode}
5762
/>`;
5863
}
5964
const svgCode = makeExampleCode('QRCodeSVG');
@@ -68,6 +73,7 @@ function FullDemo() {
6873
level,
6974
marginSize,
7075
minVersion,
76+
boostLevel,
7177
imageSettings: includeImage
7278
? {
7379
src: imageSrc,
@@ -144,6 +150,17 @@ function FullDemo() {
144150
/>
145151
</label>
146152
</div>
153+
<div>
154+
<label>
155+
Boost Level:
156+
<br />
157+
<input
158+
type="checkbox"
159+
checked={boostLevel}
160+
onChange={(e) => setBoostLevel(e.target.checked)}
161+
/>
162+
</label>
163+
</div>
147164
<div>
148165
<label>
149166
Margin Size:

Diff for: src/__test__/__snapshots__/index.test.tsx.snap

+62
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,17 @@ exports[`Canvas rendering renders Canvas variation ({ level: 'L' }) correctly 1`
293293
]
294294
`;
295295

296+
exports[`Canvas rendering renders Canvas variation ({ level: 'L', boostLevel: false }) correctly 1`] = `
297+
[
298+
<canvas
299+
height="128"
300+
role="img"
301+
style="height: 128px; width: 128px;"
302+
width="128"
303+
/>,
304+
]
305+
`;
306+
296307
exports[`Canvas rendering renders Canvas variation ({ level: 'M' }) correctly 1`] = `
297308
[
298309
<canvas
@@ -404,6 +415,17 @@ exports[`Canvas rendering renders Canvas variation ({ value: '火と氷' }) corr
404415
]
405416
`;
406417

418+
exports[`Canvas rendering renders Canvas variation ({ value: [ '12345', '/ABC/DEF', 'abcDEF123', [length]: 3 ] }) correctly 1`] = `
419+
[
420+
<canvas
421+
height="128"
422+
role="img"
423+
style="height: 128px; width: 128px;"
424+
width="128"
425+
/>,
426+
]
427+
`;
428+
407429
exports[`Canvas rendering renders basic Canvas correctly 1`] = `
408430
<canvas
409431
height="128"
@@ -846,6 +868,26 @@ exports[`SVG rendering renders SVG variation ({ level: 'L' }) correctly 1`] = `
846868
</svg>
847869
`;
848870

871+
exports[`SVG rendering renders SVG variation ({ level: 'L', boostLevel: false }) correctly 1`] = `
872+
<svg
873+
height="128"
874+
role="img"
875+
viewBox="0 0 29 29"
876+
width="128"
877+
>
878+
<path
879+
d="M0,0 h29v29H0z"
880+
fill="#ffffff"
881+
shape-rendering="crispEdges"
882+
/>
883+
<path
884+
d="M0 0h7v1H0zM12 0h1v1H12zM16 0h1v1H16zM18 0h3v1H18zM22,0 h7v1H22zM0 1h1v1H0zM6 1h1v1H6zM8 1h1v1H8zM10 1h1v1H10zM14 1h2v1H14zM22 1h1v1H22zM28,1 h1v1H28zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM11 2h1v1H11zM14 2h3v1H14zM20 2h1v1H20zM22 2h1v1H22zM24 2h3v1H24zM28,2 h1v1H28zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM8 3h8v1H8zM17 3h1v1H17zM19 3h1v1H19zM22 3h1v1H22zM24 3h3v1H24zM28,3 h1v1H28zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM9 4h1v1H9zM11 4h2v1H11zM14 4h1v1H14zM16 4h1v1H16zM19 4h2v1H19zM22 4h1v1H22zM24 4h3v1H24zM28,4 h1v1H28zM0 5h1v1H0zM6 5h1v1H6zM8 5h2v1H8zM13 5h1v1H13zM15 5h4v1H15zM22 5h1v1H22zM28,5 h1v1H28zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18 6h1v1H18zM20 6h1v1H20zM22,6 h7v1H22zM9 7h2v1H9zM14 7h1v1H14zM16 7h1v1H16zM0 8h5v1H0zM6 8h4v1H6zM11 8h3v1H11zM17 8h1v1H17zM20 8h2v1H20zM23 8h1v1H23zM25 8h1v1H25zM27 8h1v1H27zM0 9h1v1H0zM2 9h2v1H2zM5 9h1v1H5zM10 9h2v1H10zM13 9h1v1H13zM16 9h1v1H16zM18 9h3v1H18zM22 9h3v1H22zM28,9 h1v1H28zM0 10h2v1H0zM3 10h1v1H3zM5 10h2v1H5zM10 10h1v1H10zM13 10h6v1H13zM20 10h2v1H20zM23 10h2v1H23zM0 11h1v1H0zM2 11h3v1H2zM8 11h1v1H8zM11 11h1v1H11zM19 11h6v1H19zM27 11h1v1H27zM3 12h2v1H3zM6 12h10v1H6zM17 12h1v1H17zM19 12h1v1H19zM23 12h1v1H23zM25 12h2v1H25zM2 13h1v1H2zM7 13h1v1H7zM9 13h1v1H9zM11 13h1v1H11zM14 13h1v1H14zM16 13h1v1H16zM18 13h7v1H18zM26 13h1v1H26zM28,13 h1v1H28zM0 14h1v1H0zM3 14h1v1H3zM6 14h1v1H6zM10 14h1v1H10zM14 14h2v1H14zM18 14h1v1H18zM20 14h1v1H20zM24 14h1v1H24zM26 14h1v1H26zM1 15h2v1H1zM4 15h1v1H4zM10 15h3v1H10zM20 15h1v1H20zM23 15h2v1H23zM27 15h1v1H27zM5 16h2v1H5zM10 16h1v1H10zM12 16h6v1H12zM23 16h1v1H23zM26 16h1v1H26zM0 17h1v1H0zM3 17h2v1H3zM10 17h2v1H10zM14 17h1v1H14zM16 17h5v1H16zM22 17h5v1H22zM28,17 h1v1H28zM0 18h1v1H0zM2 18h2v1H2zM6 18h1v1H6zM8 18h1v1H8zM10 18h1v1H10zM12 18h1v1H12zM14 18h4v1H14zM21 18h1v1H21zM23 18h1v1H23zM25 18h2v1H25zM0 19h1v1H0zM2 19h1v1H2zM4 19h2v1H4zM7 19h5v1H7zM14 19h2v1H14zM18 19h1v1H18zM22 19h1v1H22zM24 19h1v1H24zM27 19h1v1H27zM0 20h1v1H0zM2 20h2v1H2zM5 20h2v1H5zM9 20h9v1H9zM20 20h5v1H20zM26,20 h3v1H26zM8 21h6v1H8zM16 21h5v1H16zM24,21 h5v1H24zM0 22h7v1H0zM8 22h2v1H8zM12 22h1v1H12zM14 22h4v1H14zM19 22h2v1H19zM22 22h1v1H22zM24 22h3v1H24zM0 23h1v1H0zM6 23h1v1H6zM9 23h1v1H9zM11 23h2v1H11zM14 23h1v1H14zM16 23h1v1H16zM18 23h3v1H18zM24 23h2v1H24zM27,23 h2v1H27zM0 24h1v1H0zM2 24h3v1H2zM6 24h1v1H6zM8 24h2v1H8zM13 24h3v1H13zM17 24h1v1H17zM20 24h8v1H20zM0 25h1v1H0zM2 25h3v1H2zM6 25h1v1H6zM8 25h1v1H8zM10 25h2v1H10zM16 25h3v1H16zM20 25h1v1H20zM28,25 h1v1H28zM0 26h1v1H0zM2 26h3v1H2zM6 26h1v1H6zM8 26h1v1H8zM12 26h2v1H12zM15 26h1v1H15zM18 26h2v1H18zM21 26h4v1H21zM27 26h1v1H27zM0 27h1v1H0zM6 27h1v1H6zM8 27h3v1H8zM16 27h1v1H16zM20 27h1v1H20zM22 27h4v1H22zM27 27h1v1H27zM0 28h7v1H0zM8 28h7v1H8zM17 28h1v1H17zM19 28h1v1H19zM22 28h1v1H22zM26 28h1v1H26z"
885+
fill="#000000"
886+
shape-rendering="crispEdges"
887+
/>
888+
</svg>
889+
`;
890+
849891
exports[`SVG rendering renders SVG variation ({ level: 'M' }) correctly 1`] = `
850892
<svg
851893
height="128"
@@ -1049,6 +1091,26 @@ exports[`SVG rendering renders SVG variation ({ value: '火と氷' }) correctly
10491091
</svg>
10501092
`;
10511093

1094+
exports[`SVG rendering renders SVG variation ({ value: [ '12345', '/ABC/DEF', 'abcDEF123', [length]: 3 ] }) correctly 1`] = `
1095+
<svg
1096+
height="128"
1097+
role="img"
1098+
viewBox="0 0 25 25"
1099+
width="128"
1100+
>
1101+
<path
1102+
d="M0,0 h25v25H0z"
1103+
fill="#ffffff"
1104+
shape-rendering="crispEdges"
1105+
/>
1106+
<path
1107+
d="M0 0h7v1H0zM8 0h2v1H8zM12 0h1v1H12zM15 0h2v1H15zM18,0 h7v1H18zM0 1h1v1H0zM6 1h1v1H6zM9 1h4v1H9zM15 1h2v1H15zM18 1h1v1H18zM24,1 h1v1H24zM0 2h1v1H0zM2 2h3v1H2zM6 2h1v1H6zM8 2h1v1H8zM11 2h2v1H11zM14 2h2v1H14zM18 2h1v1H18zM20 2h3v1H20zM24,2 h1v1H24zM0 3h1v1H0zM2 3h3v1H2zM6 3h1v1H6zM8 3h5v1H8zM15 3h1v1H15zM18 3h1v1H18zM20 3h3v1H20zM24,3 h1v1H24zM0 4h1v1H0zM2 4h3v1H2zM6 4h1v1H6zM9 4h2v1H9zM13 4h1v1H13zM16 4h1v1H16zM18 4h1v1H18zM20 4h3v1H20zM24,4 h1v1H24zM0 5h1v1H0zM6 5h1v1H6zM8 5h2v1H8zM11 5h2v1H11zM14 5h3v1H14zM18 5h1v1H18zM24,5 h1v1H24zM0 6h7v1H0zM8 6h1v1H8zM10 6h1v1H10zM12 6h1v1H12zM14 6h1v1H14zM16 6h1v1H16zM18,6 h7v1H18zM8 7h3v1H8zM12 7h1v1H12zM15 7h1v1H15zM1 8h1v1H1zM3 8h1v1H3zM5 8h5v1H5zM11 8h3v1H11zM16 8h4v1H16zM21 8h2v1H21zM24,8 h1v1H24zM0 9h1v1H0zM2 9h2v1H2zM5 9h1v1H5zM7 9h2v1H7zM10 9h1v1H10zM12 9h5v1H12zM23,9 h2v1H23zM3 10h4v1H3zM11 10h2v1H11zM14 10h1v1H14zM17 10h4v1H17zM1 11h2v1H1zM4 11h2v1H4zM7 11h1v1H7zM10 11h1v1H10zM12 11h2v1H12zM15 11h2v1H15zM19 11h1v1H19zM21 11h1v1H21zM23 11h1v1H23zM0 12h1v1H0zM2 12h2v1H2zM6 12h1v1H6zM8 12h2v1H8zM11 12h1v1H11zM14 12h1v1H14zM16 12h1v1H16zM18 12h1v1H18zM22,12 h3v1H22zM1 13h1v1H1zM8 13h4v1H8zM15 13h1v1H15zM17 13h1v1H17zM21 13h1v1H21zM24,13 h1v1H24zM0 14h1v1H0zM3 14h2v1H3zM6 14h2v1H6zM10 14h3v1H10zM17 14h1v1H17zM20 14h1v1H20zM22 14h2v1H22zM1 15h4v1H1zM8 15h1v1H8zM12 15h1v1H12zM20 15h1v1H20zM22 15h1v1H22zM24,15 h1v1H24zM0 16h2v1H0zM3 16h1v1H3zM6 16h2v1H6zM9 16h1v1H9zM11 16h1v1H11zM16,16 h9v1H16zM8 17h2v1H8zM11 17h2v1H11zM15 17h2v1H15zM20 17h2v1H20zM0 18h7v1H0zM8 18h1v1H8zM10 18h1v1H10zM13 18h1v1H13zM15 18h2v1H15zM18 18h1v1H18zM20 18h2v1H20zM23,18 h2v1H23zM0 19h1v1H0zM6 19h1v1H6zM8 19h1v1H8zM11 19h3v1H11zM16 19h1v1H16zM20 19h3v1H20zM0 20h1v1H0zM2 20h3v1H2zM6 20h1v1H6zM10 20h1v1H10zM12 20h1v1H12zM14 20h1v1H14zM16 20h5v1H16zM23,20 h2v1H23zM0 21h1v1H0zM2 21h3v1H2zM6 21h1v1H6zM8 21h1v1H8zM13 21h4v1H13zM19 21h1v1H19zM23 21h1v1H23zM0 22h1v1H0zM2 22h3v1H2zM6 22h1v1H6zM10 22h1v1H10zM13 22h1v1H13zM21 22h2v1H21zM24,22 h1v1H24zM0 23h1v1H0zM6 23h1v1H6zM8 23h5v1H8zM14 23h3v1H14zM22 23h2v1H22zM0 24h7v1H0zM10 24h1v1H10zM12 24h8v1H12zM24,24 h1v1H24z"
1108+
fill="#000000"
1109+
shape-rendering="crispEdges"
1110+
/>
1111+
</svg>
1112+
`;
1113+
10521114
exports[`SVG rendering renders basic SVG correctly 1`] = `
10531115
<svg
10541116
height="128"

Diff for: src/__test__/index.test.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const TEST_CONFIGS: PartialQRProps[] = [
6464
},
6565
},
6666
{value: '1234567890'},
67+
{value: ['12345', '/ABC/DEF', 'abcDEF123']},
6768
{value: 'single byte emoji ✅'},
6869
{value: 'double byte emoji 👌'},
6970
{value: 'four byte emoji 👌🏽'},
@@ -79,6 +80,7 @@ const TEST_CONFIGS: PartialQRProps[] = [
7980
// aren't encoding version anywhere testable, so this will be a proxy test
8081
// for ensuring minVersion is respected.
8182
{minVersion: 22},
83+
{level: 'L', boostLevel: false},
8284
// Test all crossOrigin values. Important in case we remove other image
8385
// settings tests and to ensure we do non-obvious things like map '' to
8486
// 'anonymous'.

Diff for: src/index.tsx

+27-6
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ type ImageSettings = {
7070

7171
type QRProps = {
7272
/**
73-
* The value to encode into the QR Code.
73+
* The value to encode into the QR Code. An array of strings can be passed in
74+
* to represent multiple segments to further optimize the QR Code.
7475
*/
75-
value: string;
76+
value: string | string[];
7677
/**
7778
* The size, in pixels, to render the QR Code.
7879
* @defaultValue 128
@@ -122,6 +123,13 @@ type QRProps = {
122123
* @defaultValue 1
123124
*/
124125
minVersion?: number;
126+
/**
127+
* If enabled, the Error Correction Level of the result may be higher than
128+
* the specified Error Correction Level option if it can be done without
129+
* increasing the version.
130+
* @defaultValue true
131+
*/
132+
boostLevel?: boolean;
125133
/**
126134
* The settings for the embedded image.
127135
*/
@@ -267,23 +275,32 @@ function useQRCode({
267275
marginSize,
268276
imageSettings,
269277
size,
278+
boostLevel,
270279
}: {
271-
value: string;
280+
value: string | string[];
272281
level: ErrorCorrectionLevel;
273282
minVersion: number;
274283
includeMargin: boolean;
275284
marginSize?: number;
276285
imageSettings?: ImageSettings;
277286
size: number;
287+
boostLevel?: boolean;
278288
}) {
279289
let qrcode = React.useMemo(() => {
280-
const segments = qrcodegen.QrSegment.makeSegments(value);
290+
const values = Array.isArray(value) ? value : [value];
291+
const segments = values.reduce<qrcodegen.QrSegment[]>((accum, v) => {
292+
accum.push(...qrcodegen.QrSegment.makeSegments(v));
293+
return accum;
294+
}, []);
281295
return qrcodegen.QrCode.encodeSegments(
282296
segments,
283297
ERROR_LEVEL_MAP[level],
284-
minVersion
298+
minVersion,
299+
undefined,
300+
undefined,
301+
boostLevel
285302
);
286-
}, [value, level, minVersion]);
303+
}, [value, level, minVersion, boostLevel]);
287304

288305
const {cells, margin, numCells, calculatedImageSettings} =
289306
React.useMemo(() => {
@@ -338,6 +355,7 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
338355
fgColor = DEFAULT_FGCOLOR,
339356
includeMargin = DEFAULT_INCLUDEMARGIN,
340357
minVersion = DEFAULT_MINVERSION,
358+
boostLevel,
341359
marginSize,
342360
imageSettings,
343361
...extraProps
@@ -370,6 +388,7 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
370388
value,
371389
level,
372390
minVersion,
391+
boostLevel,
373392
includeMargin,
374393
marginSize,
375394
imageSettings,
@@ -497,6 +516,7 @@ const QRCodeSVG = React.forwardRef<SVGSVGElement, QRPropsSVG>(
497516
fgColor = DEFAULT_FGCOLOR,
498517
includeMargin = DEFAULT_INCLUDEMARGIN,
499518
minVersion = DEFAULT_MINVERSION,
519+
boostLevel,
500520
title,
501521
marginSize,
502522
imageSettings,
@@ -507,6 +527,7 @@ const QRCodeSVG = React.forwardRef<SVGSVGElement, QRPropsSVG>(
507527
value,
508528
level,
509529
minVersion,
530+
boostLevel,
510531
includeMargin,
511532
marginSize,
512533
imageSettings,

0 commit comments

Comments
 (0)