Skip to content

Commit

Permalink
Merge pull request #49 from devtrice/feat/react-native
Browse files Browse the repository at this point in the history
add: react-native
  • Loading branch information
sudoaugustin authored Jul 27, 2024
2 parents d375cca + 30e5b1c commit f76d116
Show file tree
Hide file tree
Showing 45 changed files with 772 additions and 5,310 deletions.
110 changes: 110 additions & 0 deletions packages/react-native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
![](https://github.com/devtrice/qr-x/assets/26962987/d97e00b9-ddf1-4af7-b1b4-35cd003492d8)

# Installation

```bash
npm install @qr-x/react
```

# Usage

You can also try QR-X in action [here](https://qr-x.devtrice.dev/#playground) or follow the examples below.

## Solid

```tsx
import QRX from '@qr-x/react'

function App() {
return <QRX data='qr-x.devtrice.dev' color='#0284c7' />
}
```

## Gradient

### Linear Gradient

```tsx
import QRX from '@qr-x/react'

function App() {
return <QRX data='qr-x.devtrice.dev' gradient={{ colors: ['#f97316', '#f59e0b', '#facc15'] }} />
}
```

### Radial Gradient

```tsx
import QRX from '@qr-x/react'

function App() {
return <QRX data='qr-x.devtrice.dev' gradient={{ type: 'radial', colors: ['#f97316', '#f59e0b', '#facc15'] }} />
}
```

## Fill Image

```tsx
import QRX from '@qr-x/react'

function App() {
return <QRX data='qr-x.devtrice.dev' fillImage='https://images.unsplash.com/photo-1682687218608-5e2522b04673' />
}
```

## Brand

## Brand Image

```tsx
import QRX from '@qr-x/react'

function App() {
return (
<QRX
data='qr-x.devtrice.dev'
brand={{
src: 'https://images.unsplash.com/photo-1682687218608-5e2522b04673',
style: { width: '4rem', height: '4rem' },
}}
/>
)
}
```

## Brand Component

```tsx
import QRX from '@qr-x/react'

function App() {
return (
<QRX
data='qr-x.devtrice.dev'
brand={
<video
src='https://videos.pexels.com/video-files/8333185/8333185-hd_1080_1080_30fps.mp4'
style={{ width: '2.5rem', height: '2.5rem', border: '2px solid white', borderRadius: '50%' }}
muted
autoPlay
/>
}
/>
)
}
```

# Props

| Name | Type | Default |
| :--------------- | :--------------------------------------------------------------- | :--------- |
| data | `string` | |
| level | `'L' \| 'M' \|'Q' \| 'H'` | `'L'` |
| shapes.body | `'square' \| 'circle' \| 'leaf' \| 'rounded'` | `'square'` |
| shapes.eyeball | `'square' \| 'circle' \| 'leaf' \| 'rounded'` | `'square'` |
| shapes.eyeframe | `'square' \| 'circle' \| 'leaf' \| 'rounded'` | `'square'` |
| gradient.type | `'linear' \| 'radial'` | |
| gradients.colors | `string[] \| {value: string, stop: number}` | |
| gradients.rotate | `number` (This property only exist if gradient.type is 'radial') | `45` |
| fillImage | `string` | |
| brand | `ComponentProps<'img'>` \| `ReactNode` | |
37 changes: 37 additions & 0 deletions packages/react-native/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
meta,
data,
video,
BrandImage,
Default,
FillImage,
LinearGradient,
RadialGradient,
renderMultiple,
} from '@common/stories'
import type { Meta } from '@storybook/react'
import QRX from './index'

const Multiple = () => <div className='grid'>{renderMultiple(QRX)}</div>

const BrandComponent = () => (
<QRX
data={data}
brand={
<video
src={video}
style={{ width: '2.5rem', height: '2.5rem', border: '2px solid white', borderRadius: '50%' }}
muted
autoPlay
/>
}
/>
)

export default {
...meta,
title: 'QR-X',
component: QRX,
} satisfies Meta<typeof QRX>

export { Default, FillImage, LinearGradient, Multiple, RadialGradient, BrandImage, BrandComponent }
79 changes: 79 additions & 0 deletions packages/react-native/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { getSVGData, Options } from '@qr-x/core'
import { View, Image, ImageProps } from 'react-native'
import {
Svg,
SvgProps,
G,
Rect,
Image as SVGImage,
ForeignObject,
Defs,
Path,
LinearGradient,
Stop,
ClipPath,
} from 'react-native-svg'
import React, { ComponentProps, ReactElement, ReactNode, SVGAttributes, useEffect } from 'react'
import { RadialGradient } from '@common/stories'

type Props = SvgProps & Options & { brand?: ImageProps | ReactElement }

function useViewBox() {
const ref = React.useRef<Svg>(null)
const [size, setSize] = React.useState<{ width: number; height: number } | null>(null)

useEffect(() => {
if (ref.current) {
// const { width, height } = ref.current.getBoundingClientRect()
// setSize({ width, height })
}
}, [])

return { ref, size, viewBox: size ? `0 0 ${size.width} ${size.height}` : '' }
}

export default function QRX({ data, level, shapes, gradient, brand, fillImage, ...rest }: Props) {
const { ref, size, viewBox } = useViewBox()
const { id, path, cords, length, $gradient } = getSVGData({ data, level, shapes, gradient })

const Gradient = $gradient && $gradient.isLinearGradient ? LinearGradient : RadialGradient

return (
<Svg ref={ref} width='100%' {...rest} viewBox={`0 0 ${length} ${length}`}>
<G clipPath={`url(#${id})`}>
<Rect {...cords} fill={$gradient ? `url(#${$gradient.attributes.id})` : 'currentColor'} />
{fillImage && (
<SVGImage
{...cords}
href={fillImage}
xlinkHref={fillImage} // !Note: Must use both href and xlinkHref to link a source
preserveAspectRatio='xMidYMid slice'
/>
)}
</G>
{brand && viewBox && (
<ForeignObject {...cords}>
<Svg viewBox={viewBox}>
<ForeignObject {...cords}>
<View style={{ ...size, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{typeof brand === 'object' && 'src' in brand ? <Image {...brand} /> : (brand as ReactNode)}
</View>
</ForeignObject>
</Svg>
</ForeignObject>
)}
<Defs>
<ClipPath id={id}>
<Path d={path} />
</ClipPath>
{/* <Gradient {...$gradient?.attributes}></Gradient>
{$gradient &&
React.createElement(
,
$gradient.attributes,
$gradient.colors.map(({ color, offset }) => <Stop key={offset} offset={offset} stopColor={color} />),
)} */}
</Defs>
</Svg>
)
}
40 changes: 40 additions & 0 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@qr-x/react-native",
"version": "1.0.0",
"license": "MIT",
"description": "Create elegant QR codes",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"module": "dist/index.esm.js",
"files": [
"dist"
],
"keywords": [
"qr",
"code",
"react"
],
"scripts": {
"build": "rm -rf dist && pnpm build:dts && pnpm build:esm && pnpm build:cjs",
"build:dts": "tsc -p tsconfig.build.json --outDir ./dist",
"build:esm": "esbuild index.tsx --format=esm --outfile=./dist/index.esm.js",
"build:cjs": "esbuild index.tsx --format=cjs --outfile=./dist/index.cjs.js"
},
"dependencies": {
"@qr-x/core": "workspace:*",
"react-native": "^0.74.3",
"react-native-svg": "^15.4.0"
},
"devDependencies": {
"@common/stories": "workspace:*",
"@common/tsconfig": "workspace:*",
"@storybook/react": "^7.6.17",
"@types/react": "^18.2.39",
"esbuild": "^0.19.8",
"react": "^18.2.0",
"typescript": "^5.3.3"
},
"peerDependencies": {
"react": "^16.x || ^17.x || ^18.x"
}
}
4 changes: 4 additions & 0 deletions packages/react-native/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@common/tsconfig/build.json",
"exclude": ["node_modules", "dist", "index.stories.tsx"]
}
6 changes: 6 additions & 0 deletions packages/react-native/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "@common/tsconfig/base.json",
"compilerOptions": {
"jsx": "react-native"
}
}
Loading

0 comments on commit f76d116

Please sign in to comment.