Files
website/components/ui/shadcn-io/qr-code/index.tsx

89 lines
2.2 KiB
TypeScript

'use client';
import { formatHex, oklch } from 'culori';
import QR from 'qrcode';
import { type HTMLAttributes, useEffect, useState } from 'react';
import { cn } from '@/lib/utils';
export type QRCodeProps = HTMLAttributes<HTMLDivElement> & {
data: string;
foreground?: string;
background?: string;
robustness?: 'L' | 'M' | 'Q' | 'H';
};
const oklchRegex = /oklch\(([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\)/;
const getOklch = (color: string, fallback: [number, number, number]) => {
const oklchMatch = color.match(oklchRegex);
if (!oklchMatch) {
return { l: fallback[0], c: fallback[1], h: fallback[2] };
}
return {
l: Number.parseFloat(oklchMatch[1]),
c: Number.parseFloat(oklchMatch[2]),
h: Number.parseFloat(oklchMatch[3]),
};
};
export const QRCode = ({
data,
foreground,
background,
robustness = 'M',
className,
...props
}: QRCodeProps) => {
const [svg, setSVG] = useState<string | null>(null);
useEffect(() => {
const generateQR = async () => {
try {
const styles = getComputedStyle(document.documentElement);
const foregroundColor =
foreground ?? styles.getPropertyValue('--foreground');
const backgroundColor =
background ?? styles.getPropertyValue('--background');
const foregroundOklch = getOklch(
foregroundColor,
[0.21, 0.006, 285.885]
);
const backgroundOklch = getOklch(backgroundColor, [0.985, 0, 0]);
const newSvg = await QR.toString(data, {
type: 'svg',
color: {
dark: formatHex(oklch({ mode: 'oklch', ...foregroundOklch })),
light: formatHex(oklch({ mode: 'oklch', ...backgroundOklch })),
},
width: 200,
errorCorrectionLevel: robustness,
margin: 0,
});
setSVG(newSvg);
} catch (err) {
console.error(err);
}
};
generateQR();
}, [data, foreground, background, robustness]);
if (!svg) {
return null;
}
return (
<div
className={cn('size-full', '[&_svg]:size-full', className)}
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required for SVG"
dangerouslySetInnerHTML={{ __html: svg }}
{...props}
/>
);
};