create-react-native-library-template 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/index.mjs +62 -0
- package/package.json +41 -0
- package/template/.changeset/README.md +7 -0
- package/template/.changeset/config.json +11 -0
- package/template/.github/workflows/ci.yml +42 -0
- package/template/.github/workflows/release.yml +46 -0
- package/template/AGENTS.md +97 -0
- package/template/CLAUDE.md +5 -0
- package/template/README.md +67 -0
- package/template/apps/native/.rnstorybook/index.tsx +11 -0
- package/template/apps/native/.rnstorybook/main.ts +12 -0
- package/template/apps/native/.rnstorybook/preview.tsx +24 -0
- package/template/apps/native/app.json +17 -0
- package/template/apps/native/babel.config.js +8 -0
- package/template/apps/native/index.ts +5 -0
- package/template/apps/native/metro.config.js +18 -0
- package/template/apps/native/package.json +40 -0
- package/template/apps/native/tsconfig.json +8 -0
- package/template/apps/storybook/.storybook/main.ts +12 -0
- package/template/apps/storybook/.storybook/preview.tsx +14 -0
- package/template/apps/storybook/.storybook/vitest.setup.ts +7 -0
- package/template/apps/storybook/package.json +33 -0
- package/template/apps/storybook/tsconfig.json +7 -0
- package/template/apps/storybook/vitest.config.ts +25 -0
- package/template/biome.json +9 -0
- package/template/bun.lock +2479 -0
- package/template/gitignore +23 -0
- package/template/package.json +33 -0
- package/template/packages/ui/package.json +81 -0
- package/template/packages/ui/src/components/Button/button.stories.tsx +79 -0
- package/template/packages/ui/src/components/Button/button.test.tsx +33 -0
- package/template/packages/ui/src/components/Button/button.tsx +128 -0
- package/template/packages/ui/src/theme/tokens.ts +40 -0
- package/template/packages/ui/tsconfig.build.json +13 -0
- package/template/packages/ui/tsconfig.json +7 -0
- package/template/packages/ui/vitest.config.ts +16 -0
- package/template/packages/ui/vitest.setup.ts +1 -0
- package/template/tsconfig.base.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# create-react-native-library-template
|
|
2
|
+
|
|
3
|
+
Scaffold a publishable React Native UI library monorepo in one command:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bun create react-native-library-template my-library
|
|
7
|
+
# or: npm create react-native-library-template@latest my-library
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## What you get
|
|
11
|
+
|
|
12
|
+
- **Bun workspaces** monorepo: the library (`packages/ui`), a web Storybook (`apps/storybook`), and an Expo app running Storybook on device (`apps/native`).
|
|
13
|
+
- **Storybook 10** on web (react-native-web + Vite) and native, sharing the same CSF3 stories.
|
|
14
|
+
- **Vitest** unit tests (jsdom + react-native-web) and story tests (browser mode, `play` functions).
|
|
15
|
+
- **Biome** linting/formatting, **TypeScript** strict mode.
|
|
16
|
+
- **react-native-builder-bob** library build, **Changesets** versioning/publishing, GitHub Actions CI + release workflows.
|
|
17
|
+
|
|
18
|
+
After scaffolding:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
cd my-library
|
|
22
|
+
bun install
|
|
23
|
+
git init && git add -A && git commit -m "Initial commit"
|
|
24
|
+
bun run storybook
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See the generated `AGENTS.md` for the full development guide, including the checklist for adding new components. Source: [iv-stpn/react-native-library-template](https://github.com/iv-stpn/react-native-library-template).
|
package/index.mjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const templateDir = join(dirname(fileURLToPath(import.meta.url)), 'template');
|
|
9
|
+
const defaultName = 'my-react-native-library';
|
|
10
|
+
|
|
11
|
+
async function resolveTarget() {
|
|
12
|
+
const argument = process.argv[2];
|
|
13
|
+
if (argument) return argument;
|
|
14
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
+
const answer = (await readline.question(`Project directory (${defaultName}): `)).trim();
|
|
16
|
+
readline.close();
|
|
17
|
+
return answer || defaultName;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function restoreGitignores(directory) {
|
|
21
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
22
|
+
const entryPath = join(directory, entry.name);
|
|
23
|
+
if (entry.isDirectory()) restoreGitignores(entryPath);
|
|
24
|
+
else if (entry.name === 'gitignore') renameSync(entryPath, join(directory, '.gitignore'));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const target = await resolveTarget();
|
|
29
|
+
const targetDir = resolve(process.cwd(), target);
|
|
30
|
+
const projectName =
|
|
31
|
+
basename(targetDir)
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9-_.~]+/g, '-')
|
|
34
|
+
.replace(/^[-_.]+|[-_.]+$/g, '') || defaultName;
|
|
35
|
+
|
|
36
|
+
if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
|
|
37
|
+
console.error(`Error: ${targetDir} already exists and is not empty.`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
cpSync(templateDir, targetDir, { recursive: true });
|
|
42
|
+
restoreGitignores(targetDir);
|
|
43
|
+
|
|
44
|
+
const packageJsonPath = join(targetDir, 'package.json');
|
|
45
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
46
|
+
packageJson.name = projectName;
|
|
47
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
48
|
+
|
|
49
|
+
console.log(`
|
|
50
|
+
Scaffolded ${projectName} in ${targetDir}
|
|
51
|
+
|
|
52
|
+
Next steps:
|
|
53
|
+
cd ${target}
|
|
54
|
+
bun install
|
|
55
|
+
git init && git add -A && git commit -m "Initial commit"
|
|
56
|
+
|
|
57
|
+
Useful commands:
|
|
58
|
+
bun run storybook # web Storybook at http://localhost:6006
|
|
59
|
+
bun run storybook:native # Expo dev server for on-device Storybook
|
|
60
|
+
bun run test # unit + Storybook tests
|
|
61
|
+
See AGENTS.md for the full guide (rename the @template/* packages to your own scope).
|
|
62
|
+
`);
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-react-native-library-template",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a React Native UI library monorepo: Bun workspaces, Expo, Storybook (web + native), Vitest, Biome and Changesets",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-react-native-library-template": "index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.mjs",
|
|
12
|
+
"template"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prepack": "node scripts/prepare-template.mjs",
|
|
16
|
+
"postpack": "node -e \"require('node:fs').rmSync('template', { recursive: true, force: true })\""
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/iv-stpn/react-native-library-template.git",
|
|
21
|
+
"directory": "create"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/iv-stpn/react-native-library-template#readme",
|
|
24
|
+
"keywords": [
|
|
25
|
+
"react-native",
|
|
26
|
+
"template",
|
|
27
|
+
"create",
|
|
28
|
+
"scaffold",
|
|
29
|
+
"ui",
|
|
30
|
+
"component-library",
|
|
31
|
+
"storybook",
|
|
32
|
+
"expo",
|
|
33
|
+
"bun"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changesets
|
|
2
|
+
|
|
3
|
+
This folder is managed by [changesets](https://github.com/changesets/changesets).
|
|
4
|
+
|
|
5
|
+
Run `bun run changeset` after any user-facing change to `@template/ui`, pick a bump
|
|
6
|
+
type (patch/minor/major), and describe the change. The release workflow turns
|
|
7
|
+
accumulated changesets into a "Version Packages" PR; merging that PR publishes to npm.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "public",
|
|
8
|
+
"baseBranch": "main",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": ["@template/storybook-web", "@template/example"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ci-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
checks:
|
|
14
|
+
name: Lint, typecheck, test, build
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: oven-sh/setup-bun@v2
|
|
20
|
+
with:
|
|
21
|
+
bun-version: latest
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: bun install --frozen-lockfile
|
|
25
|
+
|
|
26
|
+
- name: Lint (biome)
|
|
27
|
+
run: bun run lint
|
|
28
|
+
|
|
29
|
+
- name: Typecheck
|
|
30
|
+
run: bun run typecheck
|
|
31
|
+
|
|
32
|
+
- name: Unit tests (vitest)
|
|
33
|
+
run: bun run test:unit
|
|
34
|
+
|
|
35
|
+
- name: Build library
|
|
36
|
+
run: bun run build
|
|
37
|
+
|
|
38
|
+
- name: Install Playwright browsers
|
|
39
|
+
run: bunx playwright install --with-deps chromium
|
|
40
|
+
|
|
41
|
+
- name: Storybook tests (vitest browser mode)
|
|
42
|
+
run: bun run test:storybook
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
concurrency: release-${{ github.ref }}
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
pull-requests: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
release:
|
|
16
|
+
name: Version or publish
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- uses: oven-sh/setup-bun@v2
|
|
24
|
+
with:
|
|
25
|
+
bun-version: latest
|
|
26
|
+
|
|
27
|
+
# `changeset publish` shells out to npm, which needs Node and registry auth.
|
|
28
|
+
- uses: actions/setup-node@v4
|
|
29
|
+
with:
|
|
30
|
+
node-version: 22
|
|
31
|
+
registry-url: https://registry.npmjs.org
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: bun install --frozen-lockfile
|
|
35
|
+
|
|
36
|
+
- name: Create release PR or publish to npm
|
|
37
|
+
uses: changesets/action@v1
|
|
38
|
+
with:
|
|
39
|
+
version: bun run version-packages
|
|
40
|
+
publish: bun run release
|
|
41
|
+
commit: 'chore: version packages'
|
|
42
|
+
title: 'chore: version packages'
|
|
43
|
+
env:
|
|
44
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
46
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Agent guide
|
|
2
|
+
|
|
3
|
+
Monorepo for a publishable React Native UI library. Bun workspaces:
|
|
4
|
+
|
|
5
|
+
| Workspace | Package | Purpose |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `packages/ui` | `@template/ui` | The library. The only published package. |
|
|
8
|
+
| `apps/storybook` | `@template/storybook-web` | Web Storybook (react-native-web + Vite) and Storybook tests (Vitest browser mode). |
|
|
9
|
+
| `apps/native` | `@template/example` | Expo app running Storybook React Native on device. |
|
|
10
|
+
|
|
11
|
+
Tooling: **bun** (package manager + script runner), **biome** (lint + format), **TypeScript 6**
|
|
12
|
+
(strict, `verbatimModuleSyntax` — use `import type` for types), **vitest** (unit tests via
|
|
13
|
+
react-native-web in jsdom; story tests via `@storybook/addon-vitest` in Chromium),
|
|
14
|
+
**react-native-builder-bob** (library build), **changesets** (versioning/publishing).
|
|
15
|
+
|
|
16
|
+
## Commands (run from the repo root)
|
|
17
|
+
|
|
18
|
+
| Command | What it does |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `bun install` | Install all workspaces. |
|
|
21
|
+
| `bun run lint` / `bun run lint:fix` | Biome check / autofix. |
|
|
22
|
+
| `bun run typecheck` | `tsc --noEmit` in every workspace. |
|
|
23
|
+
| `bun run test:unit` | Vitest unit tests in `packages/ui`. |
|
|
24
|
+
| `bun run test:storybook` | Runs every story as a Vitest browser test (needs `bunx playwright install chromium` once). |
|
|
25
|
+
| `bun run test` | Both of the above. |
|
|
26
|
+
| `bun run build` | Builds the library with bob into `packages/ui/lib`. |
|
|
27
|
+
| `bun run storybook` | Web Storybook at http://localhost:6006. |
|
|
28
|
+
| `bun run storybook:native` | Expo dev server for the on-device Storybook. |
|
|
29
|
+
| `bun run changeset` | Record a changeset for release notes/versioning. |
|
|
30
|
+
|
|
31
|
+
## Creating a new component
|
|
32
|
+
|
|
33
|
+
Follow the `Button` component (`packages/ui/src/components/Button/`) as the reference
|
|
34
|
+
implementation. For a component named `Foo`:
|
|
35
|
+
|
|
36
|
+
1. **Create the folder** `packages/ui/src/components/Foo/` containing exactly:
|
|
37
|
+
- `Foo.tsx` — the component.
|
|
38
|
+
- `Foo.test.tsx` — vitest unit tests.
|
|
39
|
+
- `Foo.stories.tsx` — Storybook stories (CSF3), including at least one `play` function.
|
|
40
|
+
- `index.ts` — re-exports: `export { Foo, type FooProps } from './Foo';`
|
|
41
|
+
2. **Write the component** (`Foo.tsx`):
|
|
42
|
+
- Named function export (`export function Foo(...)`), never a default export.
|
|
43
|
+
- Export a `FooProps` interface with a JSDoc comment per prop; document defaults with `@default`.
|
|
44
|
+
- Only import from `react`, `react-native`, and internal modules. New runtime dependencies
|
|
45
|
+
need explicit approval — they become dependencies for every consumer of the library.
|
|
46
|
+
- Use only RN APIs that exist on react-native-web (`View`, `Text`, `Pressable`,
|
|
47
|
+
`StyleSheet`, ...), otherwise unit tests and the web Storybook will break.
|
|
48
|
+
- Style with `StyleSheet.create` and the design tokens from `src/theme` (`colors`,
|
|
49
|
+
`spacing`, `radii`, `fontSizes`) — no hard-coded colors or magic numbers. Add new tokens
|
|
50
|
+
to `src/theme/tokens.ts` if needed.
|
|
51
|
+
- Accessibility is required: set `accessibilityRole`, `accessibilityState`, and accept an
|
|
52
|
+
`accessibilityLabel` prop. Also accept `style` (as `StyleProp<...>`) and `testID`.
|
|
53
|
+
3. **Export it from the library**: add `export * from './components/Foo';` to
|
|
54
|
+
`packages/ui/src/index.ts`.
|
|
55
|
+
4. **Write stories** (`Foo.stories.tsx`):
|
|
56
|
+
- CSF3 with `satisfies Meta<typeof Foo>` and `StoryObj<typeof meta>`; title `Components/Foo`.
|
|
57
|
+
- Set default `args` for every prop; use `fn()` from `storybook/test` for callbacks.
|
|
58
|
+
- One story per visual variant/state, plus interaction stories with `play` functions
|
|
59
|
+
asserting behavior (`within`, `userEvent`, `expect` from `storybook/test`).
|
|
60
|
+
- Stories are shared by the web and native Storybooks — keep them platform-neutral and
|
|
61
|
+
import types only from `@storybook/react` (type-only imports are erased at runtime).
|
|
62
|
+
5. **Write unit tests** (`Foo.test.tsx`) with `@testing-library/react`. They run in jsdom with
|
|
63
|
+
`react-native` aliased to `react-native-web`. Query by accessible role/name
|
|
64
|
+
(`screen.getByRole('button', { name: ... })`), and cover: rendering, interaction callbacks,
|
|
65
|
+
and disabled/edge states.
|
|
66
|
+
6. **Verify** from the repo root — all must pass:
|
|
67
|
+
```sh
|
|
68
|
+
bun run lint && bun run typecheck && bun run test:unit && bun run build && bun run test:storybook
|
|
69
|
+
```
|
|
70
|
+
7. **Add a changeset**: `bun run changeset` → select `@template/ui`, pick `minor` for a new
|
|
71
|
+
component (`patch` for fixes, `major` for breaking changes), write a one-line summary.
|
|
72
|
+
Commit the generated `.changeset/*.md` file with your change.
|
|
73
|
+
|
|
74
|
+
## Releasing
|
|
75
|
+
|
|
76
|
+
CI (`.github/workflows/ci.yml`) runs lint, typecheck, tests, build, and Storybook tests on
|
|
77
|
+
every PR. On pushes to `main`, `.github/workflows/release.yml` uses changesets to open/update
|
|
78
|
+
a "Version Packages" PR; merging it publishes `@template/ui` to npm (requires the `NPM_TOKEN`
|
|
79
|
+
repository secret). Never edit versions in `package.json` or `CHANGELOG.md` by hand.
|
|
80
|
+
|
|
81
|
+
## Gotchas
|
|
82
|
+
|
|
83
|
+
- `apps/native/.rnstorybook/storybook.requires.ts` is generated (by Metro or
|
|
84
|
+
`bun run --cwd apps/native storybook-generate`) and gitignored — never edit it.
|
|
85
|
+
- Test/story files are excluded from the published package and from the bob build; keep
|
|
86
|
+
runtime code out of them.
|
|
87
|
+
- The example app must stay on Expo SDK-pinned dependency versions (`bunx expo install ...`
|
|
88
|
+
from `apps/native` when adding native modules).
|
|
89
|
+
|
|
90
|
+
## Scaffolder (`create/`)
|
|
91
|
+
|
|
92
|
+
`create/` holds `create-react-native-library-template`, the npm package behind
|
|
93
|
+
`bun create react-native-library-template`. It is **not** a workspace and is not managed by
|
|
94
|
+
changesets: it has zero dependencies and is published manually. Its `prepack` script snapshots
|
|
95
|
+
every tracked file of this repo (except `create/` itself) into `create/template/` (renaming
|
|
96
|
+
`.gitignore` → `gitignore`, which the CLI reverses on scaffold); `postpack` deletes the
|
|
97
|
+
snapshot. To release it: bump `create/package.json` version, then `cd create && npm publish`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# react-native-library-template
|
|
2
|
+
|
|
3
|
+
Template for building and publishing a React Native UI component library.
|
|
4
|
+
|
|
5
|
+
- **[Bun](https://bun.sh)** — package manager, script runner, workspaces
|
|
6
|
+
- **[TypeScript 6](https://www.typescriptlang.org)** — strict mode everywhere
|
|
7
|
+
- **[Biome](https://biomejs.dev)** — linting + formatting
|
|
8
|
+
- **[Vitest](https://vitest.dev)** — unit tests (react-native-web in jsdom)
|
|
9
|
+
- **[Storybook 10](https://storybook.js.org)** — web (react-native-web + Vite) **and** on-device (Expo) demos from the same stories
|
|
10
|
+
- **Storybook tests** — every story runs as a Vitest browser-mode test; `play` functions assert interactions
|
|
11
|
+
- **[Changesets](https://github.com/changesets/changesets)** — versioning, changelogs, npm publishing via GitHub Actions
|
|
12
|
+
- **[react-native-builder-bob](https://github.com/callstack/react-native-builder-bob)** — ESM + type declarations build
|
|
13
|
+
|
|
14
|
+
## Layout
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
packages/ui/ @template/ui — the library (published)
|
|
18
|
+
apps/storybook/ Web Storybook + Storybook tests
|
|
19
|
+
apps/native/ Expo app running Storybook React Native on device
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Getting started
|
|
23
|
+
|
|
24
|
+
Scaffold a fresh project from this template:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
bun create react-native-library-template my-library
|
|
28
|
+
# or: npm create react-native-library-template@latest my-library
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or work in a clone of this repo directly:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
bun install
|
|
35
|
+
|
|
36
|
+
bun run storybook # web Storybook → http://localhost:6006
|
|
37
|
+
bun run storybook:native # Expo dev server (press i / a, or scan QR)
|
|
38
|
+
|
|
39
|
+
bun run lint # biome
|
|
40
|
+
bun run typecheck # tsc in every workspace
|
|
41
|
+
bun run test:unit # vitest unit tests
|
|
42
|
+
bunx playwright install chromium # once, for storybook tests
|
|
43
|
+
bun run test:storybook # every story as a browser test
|
|
44
|
+
bun run build # library build → packages/ui/lib
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Adding components
|
|
48
|
+
|
|
49
|
+
See [AGENTS.md](./AGENTS.md) — it documents the full checklist (component, tests, stories,
|
|
50
|
+
exports, changeset). `packages/ui/src/components/Button` is the reference implementation.
|
|
51
|
+
|
|
52
|
+
## Releasing
|
|
53
|
+
|
|
54
|
+
1. With every user-facing change, run `bun run changeset` and commit the generated file.
|
|
55
|
+
2. On merge to `main`, the release workflow opens/updates a **Version Packages** PR.
|
|
56
|
+
3. Merge that PR → `@template/ui` is built and published to npm.
|
|
57
|
+
|
|
58
|
+
Requires an `NPM_TOKEN` repository secret (npm automation token) and workflow permission to
|
|
59
|
+
create pull requests (repo Settings → Actions → General → "Allow GitHub Actions to create and
|
|
60
|
+
approve pull requests").
|
|
61
|
+
|
|
62
|
+
## Using this template
|
|
63
|
+
|
|
64
|
+
1. Rename `@template/ui` in `packages/ui/package.json` (plus its uses in the two app
|
|
65
|
+
`package.json`s, `.changeset/config.json`, root scripts, and AGENTS.md) to your package name.
|
|
66
|
+
2. Update `repository`, `license`, and `description` in `packages/ui/package.json`.
|
|
67
|
+
3. Push to GitHub, add the `NPM_TOKEN` secret, and start building components.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
import { view } from './storybook.requires';
|
|
3
|
+
|
|
4
|
+
const StorybookUIRoot = view.getStorybookUI({
|
|
5
|
+
storage: {
|
|
6
|
+
getItem: AsyncStorage.getItem,
|
|
7
|
+
setItem: AsyncStorage.setItem,
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export default StorybookUIRoot;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StorybookConfig } from '@storybook/react-native';
|
|
2
|
+
|
|
3
|
+
const main: StorybookConfig = {
|
|
4
|
+
stories: ['../../../packages/ui/src/**/*.stories.@(ts|tsx)'],
|
|
5
|
+
deviceAddons: [
|
|
6
|
+
'@storybook/addon-ondevice-controls',
|
|
7
|
+
'@storybook/addon-ondevice-actions',
|
|
8
|
+
'@storybook/addon-ondevice-backgrounds',
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default main;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Preview } from '@storybook/react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const preview: Preview = {
|
|
5
|
+
decorators: [
|
|
6
|
+
(Story) => (
|
|
7
|
+
// biome-ignore lint/plugin: exception for storybook preview
|
|
8
|
+
<View style={{ flex: 1, alignItems: 'flex-start', padding: 16 }}>
|
|
9
|
+
<Story />
|
|
10
|
+
</View>
|
|
11
|
+
),
|
|
12
|
+
],
|
|
13
|
+
parameters: {
|
|
14
|
+
backgrounds: {
|
|
15
|
+
default: 'plain',
|
|
16
|
+
values: [
|
|
17
|
+
{ name: 'plain', value: '#ffffff' },
|
|
18
|
+
{ name: 'dark', value: '#18181b' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default preview;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"name": "UI Library Example",
|
|
4
|
+
"slug": "ui-library-example",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"orientation": "portrait",
|
|
7
|
+
"userInterfaceStyle": "automatic",
|
|
8
|
+
"newArchEnabled": true,
|
|
9
|
+
"ios": {
|
|
10
|
+
"supportsTablet": true
|
|
11
|
+
},
|
|
12
|
+
"android": {
|
|
13
|
+
"edgeToEdgeEnabled": true
|
|
14
|
+
},
|
|
15
|
+
"plugins": ["@react-native-community/datetimepicker"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/noCommonJs: exception for metro config */
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { withStorybook } = require('@storybook/react-native/metro/withStorybook');
|
|
4
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
5
|
+
|
|
6
|
+
const projectRoot = import.meta.dirname;
|
|
7
|
+
const workspaceRoot = path.resolve(projectRoot, '../..');
|
|
8
|
+
|
|
9
|
+
const config = getDefaultConfig(projectRoot);
|
|
10
|
+
|
|
11
|
+
// Monorepo: watch the whole workspace and resolve hoisted dependencies.
|
|
12
|
+
config.watchFolders = [workspaceRoot];
|
|
13
|
+
config.resolver.nodeModulesPaths = [path.resolve(projectRoot, 'node_modules'), path.resolve(workspaceRoot, 'node_modules')];
|
|
14
|
+
|
|
15
|
+
module.exports = withStorybook(config, {
|
|
16
|
+
enabled: true,
|
|
17
|
+
configPath: path.resolve(projectRoot, '.rnstorybook'),
|
|
18
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@template/example",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "expo start --tunnel",
|
|
8
|
+
"android": "expo start --android",
|
|
9
|
+
"ios": "expo start --ios",
|
|
10
|
+
"storybook-generate": "sb-rn-get-stories",
|
|
11
|
+
"typecheck": "bun run storybook-generate && tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@gorhom/bottom-sheet": "^5.2.14",
|
|
15
|
+
"@react-native-async-storage/async-storage": "2.2.0",
|
|
16
|
+
"@react-native-community/datetimepicker": "8.4.4",
|
|
17
|
+
"@react-native-community/slider": "5.0.1",
|
|
18
|
+
"@storybook/addon-ondevice-actions": "^10.4.6",
|
|
19
|
+
"@storybook/addon-ondevice-backgrounds": "^10.4.6",
|
|
20
|
+
"@storybook/addon-ondevice-controls": "^10.4.6",
|
|
21
|
+
"@storybook/react": "^10.4.6",
|
|
22
|
+
"@storybook/react-native": "^10.4.6",
|
|
23
|
+
"@template/ui": "workspace:*",
|
|
24
|
+
"expo": "^54",
|
|
25
|
+
"expo-status-bar": "3.0.9",
|
|
26
|
+
"react": "19.1.0",
|
|
27
|
+
"react-native": "0.81.5",
|
|
28
|
+
"react-native-gesture-handler": "2.28.0",
|
|
29
|
+
"react-native-reanimated": "4.1.1",
|
|
30
|
+
"react-native-safe-area-context": "5.6.0",
|
|
31
|
+
"react-native-svg": "15.12.1",
|
|
32
|
+
"react-native-worklets": "0.5.1",
|
|
33
|
+
"storybook": "^10.4.6"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "~19.1.10",
|
|
37
|
+
"babel-preset-expo": "~54.0.10",
|
|
38
|
+
"typescript": "^6"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StorybookConfig } from '@storybook/react-native-web-vite';
|
|
2
|
+
|
|
3
|
+
const config: StorybookConfig = {
|
|
4
|
+
stories: ['../../../packages/ui/src/**/*.stories.@(ts|tsx)'],
|
|
5
|
+
addons: ['@storybook/addon-docs', '@storybook/addon-vitest'],
|
|
6
|
+
framework: {
|
|
7
|
+
name: '@storybook/react-native-web-vite',
|
|
8
|
+
options: {},
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default config;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@template/storybook-web",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"storybook": "storybook dev -p 6006",
|
|
7
|
+
"build-storybook": "storybook build",
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"test:watch": "vitest"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@template/ui": "workspace:*",
|
|
14
|
+
"react": "19.1.0",
|
|
15
|
+
"react-dom": "19.1.0",
|
|
16
|
+
"react-native": "0.81.5",
|
|
17
|
+
"react-native-web": "0.21.2"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@storybook/addon-docs": "^10.4.6",
|
|
21
|
+
"@storybook/addon-vitest": "^10.4.6",
|
|
22
|
+
"@storybook/react": "^10.4.6",
|
|
23
|
+
"@storybook/react-native-web-vite": "^10.4.6",
|
|
24
|
+
"@types/node": "^26",
|
|
25
|
+
"@types/react": "~19.1.10",
|
|
26
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
27
|
+
"playwright": "^1.61.1",
|
|
28
|
+
"storybook": "^10.4.6",
|
|
29
|
+
"typescript": "^6",
|
|
30
|
+
"vite": "^8.1.3",
|
|
31
|
+
"vitest": "^4.1.10"
|
|
32
|
+
}
|
|
33
|
+
}
|