Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Pull request workflow
name: npm test

on:
pull_request:
Expand All @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20.9.0'
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: '**/yarn.lock'
- name: Install dependencies
Expand Down
35 changes: 35 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5",
"importOrder": [
".*styles.css$",
"",
"dayjs",
"^react$",
"^next$",
"^next/.*$",
"<BUILTIN_MODULES>",
"<THIRD_PARTY_MODULES>",
"^@mantine/(.*)$",
"^@mantinex/(.*)$",
"^@mantine-tests/(.*)$",
"^@docs/(.*)$",
"^@/.*$",
"^../(?!.*\\.css$).*$",
"^./(?!.*\\.css$).*$",
"\\.module\\.css$",
"(?<!\\.module)\\.css$"
],
"sortPackageJson": false,
"ignorePatterns": [
"*.d.ts",
"*.mdx",
"*.md",
"packages/*/*/styles.css",
"packages/*/*/styles.layer.css",
"packages/*/*/styles/*.css",
Comment thread
gfazioli marked this conversation as resolved.
"docs/.next",
"docs/out"
]
}
35 changes: 0 additions & 35 deletions .prettierrc.mjs

This file was deleted.

17 changes: 7 additions & 10 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { dirname, join } from 'path';
import type { StorybookConfig } from '@storybook/react-vite';

function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}

const config: StorybookConfig = {
core: {
disableWhatsNewNotifications: true,
disableTelemetry: true,
enableCrashReports: false,
},
stories: ['../package/src/**/*.story.@(js|jsx|mjs|ts|tsx)'],
addons: [getAbsolutePath('@storybook/addon-essentials'), getAbsolutePath('storybook-dark-mode')],
addons: ['@storybook/addon-themes'],
framework: {
name: getAbsolutePath('@storybook/react-vite'),
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: false,
},
};

export default config;
54 changes: 21 additions & 33 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,29 @@
import type { Preview } from '@storybook/react';

import '@mantine/core/styles.css';

import React, { useEffect } from 'react';
import { addons } from '@storybook/preview-api';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
import { MantineProvider, useMantineColorScheme } from '@mantine/core';

const channel = addons.getChannel();

function ColorSchemeWrapper({ children }: { children: React.ReactNode }) {
const { setColorScheme } = useMantineColorScheme();
const handleColorScheme = (value: boolean) => setColorScheme(value ? 'dark' : 'light');
import { MantineProvider } from '@mantine/core';

useEffect(() => {
channel.on(DARK_MODE_EVENT_NAME, handleColorScheme);
return () => channel.off(DARK_MODE_EVENT_NAME, handleColorScheme);
}, [channel]);

return <>{children}</>;
}

export const decorators = [
(renderStory: any) => <ColorSchemeWrapper>{renderStory()}</ColorSchemeWrapper>,
(renderStory: any) => <MantineProvider>{renderStory()}</MantineProvider>,
];
export const parameters = {
layout: 'centered',
};

const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Mantine color scheme',
defaultValue: 'light',
toolbar: {
icon: 'mirror',
items: [
{ value: 'light', title: 'Light' },
{ value: 'dark', title: 'Dark' },
],
},
},
};

export default preview;
export const decorators = [
(renderStory: any, context: any) => {
const scheme = (context.globals.theme || 'light') as 'light' | 'dark';
return <MantineProvider forceColorScheme={scheme}>{renderStory()}</MantineProvider>;
},
];
125 changes: 41 additions & 84 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,94 +1,51 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What This Repo Is

This is the `@gfazioli/mantine-qr-code` component library, part of the Mantine Extensions ecosystem. It provides a QR Code component for React applications built with Mantine.
## Project
`@gfazioli/mantine-qr-code` — A Mantine 9 QR Code component for React with custom dot/finder shapes, image overlay, SVG/PNG download hook, and full Styles API. Requires React 19 and TypeScript 6.

## Commands

| Command | Purpose |
|---------|---------|
| `yarn build` | Build the npm package (Rollup + DTS + CSS extraction) |
| `yarn test` | Full test suite: syncpack → prettier → typecheck → lint → jest |
| `yarn build` | Build the npm package via Rollup |
| `yarn dev` | Start the Next.js docs dev server (port 9281) |
| `yarn test` | Full test suite (syncpack + oxfmt + typecheck + lint + jest) |
| `yarn jest` | Run only Jest unit tests |
| `yarn jest --testPathPattern=QRCode` | Run tests for a single component |
| `yarn dev` | Start docs dev server at http://localhost:9281 |
| `yarn storybook` | Start Storybook at http://localhost:8271 |
| `yarn docgen` | Generate `docs/docgen.json` from component TypeScript types |
| `yarn docs:build` | Run docgen + build Next.js static site |
| `yarn docs:deploy` | Build docs + deploy to GitHub Pages |
| `yarn prettier:write` | Auto-fix formatting (run this if prettier check fails after template propagation) |
| `yarn release:patch` | Bump patch version, publish to npm, deploy docs |
| `diny yolo` | AI-assisted git commit (stages all, generates message, commits + pushes) |
| `yarn docgen` | Generate component API docs (docgen.json) |
| `yarn docs:build` | Build the Next.js docs site for production |
| `yarn docs:deploy` | Build and deploy docs to GitHub Pages |
| `yarn lint` | Run oxlint + Stylelint |
| `yarn format:write` | Format all files with oxfmt |
| `yarn storybook` | Start Storybook dev server |
| `yarn clean` | Remove build artifacts |
| `yarn release:patch` | Bump patch version and deploy docs |
| `diny yolo` | AI-assisted commit (stage all, generate message, commit + push) |

> **Important**: After changing the public API, always run `yarn clean && yarn build` before `yarn test`.

## Architecture

**Monorepo with two Yarn workspaces:**

- `package/` — The publishable npm package. Source lives in `package/src/`. Built artifacts go to `package/dist/` (ESM `.mjs`, CJS `.cjs`, `.d.ts`, `styles.css`).
- `docs/` — Next.js 15 static site with MDX support. Uses `workspace:*` to reference the local package. Deployed to GitHub Pages via `gh-pages`.

**Build pipeline** (`yarn build`):
1. Rollup bundles `package/src/index.ts` → ESM + CJS with `preserveModules`
2. `scripts/generate-dts.ts` produces `.d.ts` and `.d.mts` declarations
3. `scripts/prepare-css.ts` extracts CSS into `styles.css` and `styles.layer.css`

CSS class names are hashed with the prefix `me` via `hash-css-selector`. Non-index chunks get a `'use client';` banner automatically.

## Component Authoring Pattern

Every component follows Mantine's Styles API pattern. Use the QRCode component (`package/src/QRCode.tsx`) as the canonical reference:

1. **Factory type** — Define a `PolymorphicFactory` type specifying props, default element, stylesNames, variants, and CSS variables.
2. **Props interface** — Extend `BoxProps` + your base props + `StylesApiProps<YourFactory>`.
3. **Default props** — Declare a `defaultProps` partial object.
4. **CSS Variables resolver** — Use `createVarsResolver<YourFactory>()` to map props to CSS custom properties.
5. **Component body** — Use `polymorphicFactory()` with `useProps()` and `useStyles()` hooks. Render via Mantine's `Box` with `getStyles('partName')` and `mod` for data attributes.
6. **Attach classes** — Set `Component.classes = classes` and `Component.displayName`.

### Exports pattern (`package/src/index.ts`)
Export the component and its public types (base props, CSS variables type, factory type). Do not export internal types.

### CSS Modules (`Component.module.css`)
- Use CSS custom properties (`--component-*`) for all dynamic values
- Define sizes via `--component-size-{xs,sm,md,lg,xl}`
- Use `[data-attribute]` selectors for state/variant styling
- Animations go in `@keyframes` within the module

### Testing (`Component.test.tsx`)
- Import `render` from `@mantine-tests/core` (not directly from `@testing-library/react`)
- Test: renders without crashing, forwards ref, data attributes for props/variants/states
- Jest with jsdom; CSS modules are mocked via `identity-obj-proxy`

### Storybook (`Component.story.tsx`)
Stories live alongside source. Storybook runs on port 8271.

## Documentation Site

- `docs/data.ts` — Package metadata (name, description, repo URL, author)
- `docs/docs.mdx` — Main documentation content
- `docs/demos/` — Interactive demos using `@mantinex/demo`
- `docs/pages/index.tsx` — Assembles Shell, PageHeader, DocsTabs, and the MDX content
- `docs/styles-api/` — Styles API data for the documentation table
- `docs/docgen.json` — Auto-generated from TypeScript types (don't edit manually)

The `next.config.mjs` dynamically sets `basePath` from the repository field in `package/package.json`.

## Code Style

- Prettier: 160 char width, single quotes, trailing commas, sorted imports (styles → react → third-party → @mantine → local)
- MDX files use 70 char print width
- ESLint: `eslint-config-mantine` base
- Stylelint: `stylelint-config-standard-scss` (relaxed)
- Syncpack enforces consistent dependency versions across workspaces

## Tech Stack

- **Mantine 8.x**, **React 19**, **TypeScript 5.9**
- **Yarn 4** (node-modules linker, not PnP)
- **Rollup** for package builds, **esbuild** for transpilation
- **Next.js 15** with static export for docs
- **Jest 29** with jsdom for tests
- **Storybook 8** with React-Vite framework
### Workspace Layout
Yarn workspaces monorepo with two workspaces: `package/` (npm package) and `docs/` (Next.js 15 documentation site).

### Package Source (`package/src/`)
- `QRCode.tsx` -- Main component using Mantine's `polymorphicFactory()` pattern with `useProps()` and `useStyles()` hooks.
- `QRCode.module.css` -- CSS module with custom properties for dynamic values.
- `use-qr-code-download.ts` -- Hook for downloading the QR code as an image.
- `lib/qr-encoder.ts` -- Custom QR code encoding logic.
- `lib/dot-shapes.ts` -- Configurable dot shape renderers.
- `lib/finder-shapes.ts` -- Configurable finder pattern shape renderers.
- `lib/utils.ts` -- Shared utilities.
- `index.ts` -- Public exports (component, hook, types).

### Build Pipeline
Rollup bundles to dual ESM/CJS with `'use client'` banner. CSS modules hashed with `hash-css-selector` (prefix `me`). TypeScript declarations via `rollup-plugin-dts`. CSS split into `styles.css` and `styles.layer.css`.

## Testing
Jest with `jsdom`, `esbuild-jest` transform, CSS mocked via `identity-obj-proxy`. Tests use `@mantine-tests/core` render helper.

## Ecosystem
This repo is part of the Mantine Extensions ecosystem, derived from the `mantine-base-component` template. See the workspace `CLAUDE.md` (in the parent directory) for:
- Development checklist (code -> test -> build -> docs -> release)
- Cross-cutting patterns (compound components, responsive CSS, GitHub sync)
- Update packages workflow
- Release process
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
## Overview

This component is created on top of the [Mantine](https://mantine.dev/) library.
It requires **Mantine 9.x** and **React 19**.
Comment thread
gfazioli marked this conversation as resolved.

The [Mantine QR Code](https://gfazioli.github.io/mantine-qr-code/) component is a highly customizable QR Code generator for React applications built with Mantine.

Expand Down
17 changes: 8 additions & 9 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,30 @@
},
"dependencies": {
"@gfazioli/mantine-qr-code": "workspace:*",
"@mantine/code-highlight": "8.3.16",
"@mantine/core": "8.3.16",
"@mantine/hooks": "8.3.16",
"@mantine/code-highlight": "9.0.0",
"@mantine/core": "9.0.0",
"@mantine/hooks": "9.0.0",
"@mantinex/demo": "^2.0.0",
"@mantinex/dev-icons": "^2.0.0",
"@mantinex/mantine-header": "^2.0.0",
"@mantinex/mantine-logo": "^2.0.0",
"@mantinex/mantine-meta": "^2.0.0",
"@mantinex/shiki": "^1.1.0",
"@mdx-js/loader": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@next/mdx": "^15.5.12",
"@tabler/icons-react": "^3.40.0",
"@next/mdx": "^15.5.14",
"@tabler/icons-react": "^3.41.1",
"@types/mdx": "^2.0.13",
"next": "15.5.12",
"next": "15.5.14",
"react": "19.2.4",
"react-dom": "19.2.4",
"remark-slug": "^7.0.1",
"shiki": "^3.23.0",
"type-fest": "^4.41.0"
},
"devDependencies": {
"@types/node": "^22.19.15",
"@types/node": "^22.19.17",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"typescript": "5.9.3"
"typescript": "6.0.2"
}
}
20 changes: 15 additions & 5 deletions docs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"target": "es2015",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
Loading
Loading