diff --git a/.gitignore b/.gitignore index e90e9c9..c2a4bf1 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,6 @@ typings/ # automatically generated for any operations where npm modifies package-lock.json + +# Storybook +storybook-static/ diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..4dd43fb --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,16 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: { + name: '@storybook/react-vite', + options: {}, + }, + docs: {}, +}; +export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 0000000..adcda96 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,14 @@ +import type { Preview } from '@storybook/react'; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview; diff --git a/package.json b/package.json index 46c30ca..57741e8 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "types": "dist/esm/index.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc" + "build": "tsc", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "repository": { "type": "git", @@ -29,11 +31,21 @@ }, "homepage": "https://github.com/gcoro/react-qrcode-logo#readme", "devDependencies": { + "@storybook/addon-essentials": "^8.6.14", + "@storybook/addon-interactions": "^8.6.14", + "@storybook/addon-links": "^8.6.15", + "@storybook/blocks": "^8.6.14", + "@storybook/react": "^8.6.15", + "@storybook/react-vite": "^8.6.15", + "@storybook/test": "^8.6.15", + "@types/node": "^25.0.9", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", - "typescript": "^5.4.5", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "storybook": "^8.6.15", + "typescript": "^5.4.5", + "vite": "^7.3.1" }, "dependencies": { "qrcode-generator": "^2.0.4" @@ -42,4 +54,4 @@ "react": ">=18.0.0", "react-dom": ">=18.0.0" } -} \ No newline at end of file +} diff --git a/stories/QRCode.stories.tsx b/stories/QRCode.stories.tsx new file mode 100644 index 0000000..34120d2 --- /dev/null +++ b/stories/QRCode.stories.tsx @@ -0,0 +1,218 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { QRCode } from '../lib/index'; + +const meta: Meta = { + title: 'Components/QRCode', + component: QRCode, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + value: { + control: 'text', + description: 'The value encoded in the QR Code', + }, + size: { + control: { type: 'range', min: 100, max: 500, step: 10 }, + description: 'The size of the QR Code in pixels', + }, + bgColor: { + control: 'color', + description: 'Background color', + }, + fgColor: { + control: 'color', + description: 'Foreground color', + }, + qrStyle: { + control: 'select', + options: ['squares', 'dots', 'fluid'], + description: 'Style of the QR Code modules', + }, + logoImage: { + control: 'text', + description: 'Logo image URL or base64', + }, + logoWidth: { + control: { type: 'range', min: 0, max: 200, step: 5 }, + description: 'Logo width in pixels', + }, + logoHeight: { + control: { type: 'range', min: 0, max: 200, step: 5 }, + description: 'Logo height in pixels', + }, + logoOpacity: { + control: { type: 'range', min: 0, max: 1, step: 0.1 }, + description: 'Logo opacity (0-1)', + }, + eyeRadius: { + control: 'object', + description: 'Corner radius for the positioning patterns (eyes). Can be a number, array of numbers, or array of objects with outer/inner properties.', + }, + quietZone: { + control: { type: 'range', min: 0, max: 50, step: 5 }, + description: 'Size of the quiet zone around the QR Code', + }, + removeQrCodeBehindLogo: { + control: 'boolean', + description: 'Remove QR cells behind the logo', + }, + enableCORS: { + control: 'boolean', + description: 'Enable CORS for logo image', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// Default story with basic configuration +export const Default: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 200, + }, +}; + +// With logo +export const WithLogo: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 250, + logoImage: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/200px-React-icon.svg.png', + logoWidth: 60, + logoHeight: 60, + enableCORS: true, + }, +}; + +// Dots style +export const DotsStyle: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 200, + qrStyle: 'dots', + fgColor: '#8B5CF6', + }, +}; + +// Fluid style +export const FluidStyle: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 200, + qrStyle: 'fluid', + fgColor: '#10B981', + }, +}; + +// Custom colors +export const CustomColors: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 200, + bgColor: '#1F2937', + fgColor: '#F59E0B', + }, +}; + +// Rounded eyes +export const RoundedEyes: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 200, + eyeRadius: 20, + fgColor: '#EF4444', + }, +}; + +// Rounded outer with sharp inner pupils +export const RoundedOuterSharpInner: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 250, + eyeRadius: [ + { outer: [10, 10, 10, 10], inner: [0, 0, 0, 0] }, + { outer: [10, 10, 10, 10], inner: [0, 0, 0, 0] }, + { outer: [10, 10, 10, 10], inner: [0, 0, 0, 0] }, + ], + fgColor: '#3B82F6', + }, + name: 'Rounded Outer, Sharp Inner', +}; + +// Sharp outer with rounded inner pupils +export const SharpOuterRoundedInner: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 250, + eyeRadius: [ + { outer: [0, 0, 0, 0], inner: [10, 10, 10, 10] }, + { outer: [0, 0, 0, 0], inner: [10, 10, 10, 10] }, + { outer: [0, 0, 0, 0], inner: [10, 10, 10, 10] }, + ], + fgColor: '#3B82F6', + }, + name: 'Sharp Outer, Rounded Inner', +}; + +// With logo and custom inner/outer radius +export const LogoWithCustomEyeRadius: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 300, + logoImage: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/200px-React-icon.svg.png', + logoWidth: 70, + logoHeight: 70, + enableCORS: true, + eyeRadius: [ + { outer: [12, 12, 12, 12], inner: [8, 8, 8, 8] }, + { outer: [12, 12, 12, 12], inner: [8, 8, 8, 8] }, + { outer: [12, 12, 12, 12], inner: [8, 8, 8, 8] }, + ], + removeQrCodeBehindLogo: true, + }, +}; + +// Complex example with all features +export const ComplexExample: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 300, + logoImage: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/200px-React-icon.svg.png', + logoWidth: 60, + logoHeight: 60, + logoOpacity: 0.9, + enableCORS: true, + qrStyle: 'fluid', + eyeRadius: [ + { outer: [15, 15, 15, 15], inner: [10, 10, 10, 10] }, + { outer: [15, 15, 15, 15], inner: [10, 10, 10, 10] }, + { outer: [15, 15, 15, 15], inner: [10, 10, 10, 10] }, + ], + fgColor: '#6366F1', + bgColor: '#F3F4F6', + quietZone: 20, + removeQrCodeBehindLogo: true, + }, +}; + +// Custom eye colors +export const CustomEyeColors: Story = { + args: { + value: 'https://github.com/gcoro/react-qrcode-logo', + size: 250, + eyeColor: [ + { outer: '#FF0000', inner: '#0000FF' }, + { outer: '#00FF00', inner: '#FF00FF' }, + { outer: '#0000FF', inner: '#FFFF00' }, + ], + eyeRadius: [ + { outer: [5, 5, 5, 5], inner: [5, 5, 5, 5] }, + { outer: [5, 5, 5, 5], inner: [5, 5, 5, 5] }, + { outer: [5, 5, 5, 5], inner: [5, 5, 5, 5] }, + ], + }, +}; diff --git a/stories/README.md b/stories/README.md new file mode 100644 index 0000000..5a4278b --- /dev/null +++ b/stories/README.md @@ -0,0 +1,67 @@ +# Storybook for react-qrcode-logo + +This directory contains Storybook stories for developing and testing the QRCode component. + +## Running Storybook + +From the root directory: + +```bash +npm run storybook +``` + +This will start Storybook on `http://localhost:6006` + +## Building Storybook + +To build a static version of Storybook: + +```bash +npm run build-storybook +``` + +The output will be in the `storybook-static` directory. + +## Available Stories + +The `QRCode.stories.tsx` file includes comprehensive stories demonstrating: + +- **Default**: Basic QR code +- **WithLogo**: QR code with a logo +- **DotsStyle**: Using the 'dots' qrStyle +- **FluidStyle**: Using the 'fluid' qrStyle +- **CustomColors**: Custom foreground and background colors +- **RoundedEyes**: Rounded corner eyes with eyeRadius +- **DefaultPupils**: Pupils inherit rounding from eyeRadius (default behavior) +- **SharpPupils**: Pupils with sharp corners (squarePupilRadius=0) +- **RoundedPupils**: Pupils with independent rounded corners +- **LogoWithSquarePupils**: Combining logo with custom pupil radius +- **LogoWithSquarePupilsAndZeroRadius**: Logo with sharp pupils +- **ComplexExample**: All features combined +- **CustomEyeColors**: Different colors for each eye + +## Controls + +Storybook's Controls addon allows you to interactively adjust all props: + +- **value**: The data encoded in the QR code +- **size**: QR code size in pixels +- **bgColor/fgColor**: Background and foreground colors +- **qrStyle**: squares, dots, or fluid +- **logoImage**: URL or base64 logo +- **eyeRadius**: Corner radius for eye outer rings +- **squarePupilRadius**: Corner radius for pupils (undefined = inherit from eyeRadius) +- And many more... + +## Adding New Stories + +To add new stories, edit `QRCode.stories.tsx` and follow the existing pattern: + +```typescript +export const YourStory: Story = { + args: { + value: 'Your QR code data', + // ... other props + }, +}; +``` diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index c006855..82bf395 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -5,5 +5,6 @@ "downlevelIteration": true, /* Provide full support for iterables in for..of, spread, and destructuring when targeting ES5 or ES3. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "outDir": "./dist/cjs" /* Redirect output structure to the directory. */ - } + }, + "exclude": ["playground", "stories", ".storybook"] } \ No newline at end of file diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 7eab7d5..0cbcc99 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -5,5 +5,6 @@ "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "outDir": "./dist/esm", /* Redirect output structure to the directory. */ "moduleResolution": "node", /* Tells the compiler how to look up and resolve imported modules */ - } + }, + "exclude": ["playground", "stories", ".storybook"] } \ No newline at end of file