avin / sexy-qr

Sexy SVG QR-code generator
https://avin.github.io/sexy-qr/
41 stars 10 forks source link

React integration requires dangerouslySetInnerHTML + svg() output is inflexible #3

Open firxworx opened 1 month ago

firxworx commented 1 month ago

Beautiful library, it actually works... and then... The only way to use the SVG that it creates with React is with dangerouslySetInnerHTML! Nooooo! :)

These days one has to be mindful about the potential for supply chain attacks especially with npm packages these days so that makes it hard to use this library as published. I suppose one could add DOMPurify or work with a fork since you published as MIT (thank-you!).

Suggestions --

An option to get an SVG data URL to load into an image tag would be a nice feature request.

QRSvg.ts could be split up as helpers so a React component could import and use them and do that final step of constructing the SVG element.

This would also make it nicely (and safely!) compatible with pretty much any UI framework and vanillajs.

I think this step of the svg() method should be something that downstream consumers of the library should be able to do on their own. This will also enable them to easily add things like <title>, omit a hardcoded width and height for fluid/responsive sizes driven by css, and other tidbits to meet various requirements.

    return `\
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}" fill="${fill}">
${this.svgAdditionalContent(this.options.preContent)}
${this.paths.join('\n')}
${this.svgAdditionalContent(this.options.postContent)}
</svg>`;

Right now its not possible because of how QRSvg is implemented as a class.

I suppose you could always publish a React component too since the "guts" are practically already there!

I imagine you created this library for a project you were working on, so thank-you kindly for taking the time to publish and share it to the world.

avin commented 1 month ago

Hey! You can use this construction:

`data:image/svg+xml;base64,${btoa(qrSvg.svg)}`

Full react component may be like this:

import React, { useMemo } from 'react';
import { QRCode, QRSvg } from 'sexy-qr';

interface Props extends React.HTMLAttributes<HTMLImageElement> {
  payload: string;
  size: number;
  radiusFactor?: number;
}

const Qr = ({ payload, size, radiusFactor = 1, ...props }: Props): JSX.Element => {
  const imgSrc = useMemo(() => {
    const qrCode = new QRCode({
      content: payload,
      ecl: 'M', // 'L' | 'M' | 'Q' | 'H'
    });

    const qrSvg = new QRSvg(qrCode, {
      fill: '#000',
      cornerBlocksAsCircles: false,
      size, // px
      radiusFactor, // 0-1
      cornerBlockRadiusFactor: 2, // 0-3
      roundOuterCorners: true,
      roundInnerCorners: false,
      preContent: `<!-- QR Content: ${payload} -->`,
    });

    return `data:image/svg+xml;base64,${btoa(qrSvg.svg)}`;
  }, [payload, isDarkTheme, size, radiusFactor]);

  return <img {...props} src={imgSrc} alt="QR" />;
};

export default Qr;