Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/public/images/x-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/docs/src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const PAGES = [
{ path: '/installation', priority: 0.8 },
{ path: '/quick-start', priority: 0.8 },
{ path: '/demo', priority: 0.9 },
{ path: '/x-qr-code', priority: 0.8 },
{ path: '/llms-txt', priority: 0.5 },
{ path: '/react-qr-code-props', priority: 0.7 },
{ path: '/data-modules-settings', priority: 0.7 },
Expand Down
127 changes: 127 additions & 0 deletions apps/docs/src/app/x-qr-code/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import type { Metadata } from 'next'

import { CodeEditor } from '@/components/code-editor'
import { XQrCodeDemo } from '@/components/demo/x-qr-code-demo'
import { PageHeading } from '@/components/page-heading'
import {
TypographyH2,
TypographyH3,
TypographyInlineCode,
TypographyList,
TypographyP,
} from '@/components/ui/typography'

export const metadata: Metadata = {
title: 'X (Twitter) QR Code Generator',
description:
'Create a free, custom QR code for your X (Twitter) profile with a "Follow" frame, the X logo and your own colors. Download it as PNG or SVG.',
}

const faqs = [
{
question: 'Does X still have a built-in profile QR code?',
answer:
'Twitter used to show a QR code for your profile in its mobile apps, but the X apps no longer offer it. A generator like this one is now the easiest way to get a scannable code for your profile.',
},
{
question: 'What happens if I change my X handle?',
answer:
'The QR code points to x.com/yourhandle, so changing your handle breaks it. Generate and print a new code after renaming your account.',
},
{
question: 'Is it free?',
answer:
'Yes. The QR code is generated in your browser, has no watermark and never expires, because it links straight to your profile with no redirect in between.',
},
{
question: 'Which format should I download?',
answer:
'Use SVG for print (badges, stickers, posters) since it scales without losing quality, and PNG for slides, websites and social posts.',
},
]

const faqJsonLd = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqs.map(({ question, answer }) => ({
'@type': 'Question',
name: question,
acceptedAnswer: { '@type': 'Answer', text: answer },
})),
}

const codeBlock = `
import { ReactQRCode } from '@lglab/react-qr-code'

export const XQrCode = ({ handle }: { handle: string }) => (
<ReactQRCode
value={\`https://x.com/\${handle}\`}
level='Q'
dataModulesSettings={{ style: 'rounded' }}
finderPatternOuterSettings={{ style: 'rounded-lg' }}
finderPatternInnerSettings={{ style: 'rounded' }}
imageSettings={{ src: '/x-logo.svg', width: 64, height: 64, excavate: true }}
frameSettings={{ style: 'banner-bottom', text: \`Follow @\${handle}\` }}
/>
)
`

export default function Page() {
return (
<>
<script
type='application/ld+json'
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqJsonLd) }}
/>
<PageHeading
heading='X (Twitter) QR Code Generator'
subheading='Make a QR code that opens your X profile, with a "Follow" frame and your own style.'
/>
<TypographyP>
Enter your handle, tweak any setting below, and download your code as PNG or SVG.
Everything runs in your browser and the code links straight to your profile, so it
never expires.
</TypographyP>
<div className='mt-6'>
<XQrCodeDemo />
</div>

<TypographyH2>Where to use your X QR code</TypographyH2>
<TypographyList
items={[
{
key: 'badges',
content:
'Conference and meetup badges, so people can follow you on the spot.',
},
{
key: 'slides',
content: 'The last slide of a talk, where everyone has their phone out.',
},
{ key: 'cards', content: 'Business cards, flyers and posters.' },
{ key: 'merch', content: 'Stickers, t-shirts, laptop lids and packaging.' },
{
key: 'stream',
content: 'Video and livestream overlays, or the end screen of a video.',
},
]}
/>

<TypographyH2>Build it into your own app</TypographyH2>
<TypographyP>
This generator is built with{' '}
<TypographyInlineCode>@lglab/react-qr-code</TypographyInlineCode>, a free and
open-source React component. Here is the same X preset in a few lines of code:
</TypographyP>
<CodeEditor code={codeBlock} />

<TypographyH2>FAQ</TypographyH2>
{faqs.map(({ question, answer }) => (
<div key={question}>
<TypographyH3>{question}</TypographyH3>
<TypographyP>{answer}</TypographyP>
</div>
))}
</>
)
}
72 changes: 46 additions & 26 deletions apps/docs/src/components/demo/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import {
type ReactQRCodeRef,
} from '@lglab/react-qr-code'
import { ChevronDown } from 'lucide-react'
import { type PropsWithChildren, useRef, useState } from 'react'
import {
type Dispatch,
type PropsWithChildren,
type ReactNode,
type SetStateAction,
useRef,
useState,
} from 'react'

import {
Accordion,
Expand Down Expand Up @@ -70,38 +77,51 @@ const AccContent = ({ children }: PropsWithChildren) => (
<AccordionContent className='p-2 pb-5 space-y-5'>{children}</AccordionContent>
)

export const Demo = () => {
export const defaultQrProps: ReactQRCodeProps = {
value: 'https://reactqrcode.com',
size: 400,
level: 'M',
marginSize: 3,
minVersion: 1,
boostLevel: true,
background: '#FFFFFF',
dataModulesSettings: {
style: 'square',
color: '#000000',
randomSize: false,
},
finderPatternOuterSettings: {
style: 'square',
color: '#000000',
},
finderPatternInnerSettings: {
style: 'square',
color: '#000000',
},
imageSettings: defaultImageSettings,
}

interface DemoProps {
initialProps?: ReactQRCodeProps
downloadName?: string
header?: (setQrProps: Dispatch<SetStateAction<ReactQRCodeProps>>) => ReactNode
}

export const Demo = ({
initialProps = defaultQrProps,
downloadName = 'qr-code',
header,
}: DemoProps) => {
const qrRef = useRef<ReactQRCodeRef>(null)
const [qrProps, setQrProps] = useState<ReactQRCodeProps>({
value: 'https://reactqrcode.com',
size: 400,
level: 'M',
marginSize: 3,
minVersion: 1,
boostLevel: true,
background: '#FFFFFF',
dataModulesSettings: {
style: 'square',
color: '#000000',
randomSize: false,
},
finderPatternOuterSettings: {
style: 'square',
color: '#000000',
},
finderPatternInnerSettings: {
style: 'square',
color: '#000000',
},
imageSettings: defaultImageSettings,
})
const [qrProps, setQrProps] = useState<ReactQRCodeProps>(initialProps)

const download = (format: DownloadFileFormat) =>
qrRef.current?.download({ name: 'qr-code', format, size: 1000 })
qrRef.current?.download({ name: downloadName, format, size: 1000 })

return (
<div className='grid grid-cols-1 gap-4 sm:grid-cols-5'>
<div className='sm:col-span-3'>
{header?.(setQrProps)}
<Accordion type='single' collapsible className='w-full'>
<AccordionItem value='main-props'>
<AccordionTrigger>Main props</AccordionTrigger>
Expand Down
104 changes: 104 additions & 0 deletions apps/docs/src/components/demo/x-qr-code-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use client'

import { type FrameSettings, type ReactQRCodeProps } from '@lglab/react-qr-code'
import { type Dispatch, type SetStateAction, useState } from 'react'

import { FormField } from '@/components/ui/form-fields'
import { Input } from '@/components/ui/input'

import { Demo } from './demo'

const xProfileUrl = (handle: string) =>
handle ? `https://x.com/${handle}` : 'https://x.com'

const xFollowText = (handle: string) => (handle ? `Follow @${handle}` : 'Follow me on X')

// Accepts "handle", "@handle" or a pasted profile URL
const normalizeHandle = (input: string) =>
input
.trim()
.replace(/^@/, '')
.replace(/^(https?:\/\/)?(www\.)?(x|twitter)\.com\//i, '')
.replace(/[/?#].*$/, '')
.replace(/[^A-Za-z0-9_]/g, '')
.slice(0, 15)

interface XHandleProps {
setQrProps: Dispatch<SetStateAction<ReactQRCodeProps>>
}

const XHandle = ({ setQrProps }: XHandleProps) => {
const [handle, setHandle] = useState('')

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const next = normalizeHandle(e.target.value)
setHandle(next)
setQrProps((prevProps) => ({
...prevProps,
value: xProfileUrl(next),
frameSettings: prevProps.frameSettings && {
...prevProps.frameSettings,
text: xFollowText(next),
},
}))
}

return (
<div className='mb-4 rounded-lg border p-4'>
<FormField label='Your X handle'>
<Input
type='text'
placeholder='@yourhandle'
value={handle ? `@${handle}` : ''}
onChange={onChange}
aria-label='Your X handle'
/>
</FormField>
</div>
)
}

const xFrameSettings: FrameSettings = {
style: 'banner-bottom',
text: xFollowText(''),
color: '#000000',
}

const xQrProps: ReactQRCodeProps = {
value: xProfileUrl(''),
size: 400,
level: 'Q',
marginSize: 3,
minVersion: 1,
boostLevel: true,
background: '#FFFFFF',
dataModulesSettings: {
style: 'rounded',
color: '#000000',
randomSize: false,
},
finderPatternOuterSettings: {
style: 'rounded-lg',
color: '#000000',
},
finderPatternInnerSettings: {
style: 'rounded',
color: '#000000',
},
imageSettings: {
src: '/images/x-logo.svg',
width: 64,
height: 64,
excavate: true,
opacity: 1,
},
frameSettings: xFrameSettings,
}

export const XQrCodeDemo = () => (
<Demo
initialProps={xQrProps}
downloadName='x-qr-code'
header={(setQrProps) => <XHandle setQrProps={setQrProps} />}
/>
)
4 changes: 4 additions & 0 deletions apps/docs/src/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export const mainNav: MenuItemProps[] = [
title: 'Demo',
url: '/demo',
},
{
title: 'X QR Code',
url: '/x-qr-code',
},
{
title: 'llms.txt',
url: '/llms-txt',
Expand Down
Loading