Skip to content

Commit a51fae9

Browse files
committed
readme + ts setup
1 parent 58c3af4 commit a51fae9

File tree

4 files changed

+305
-0
lines changed

4 files changed

+305
-0
lines changed

README.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
2+
# React Resize Detector Context
3+
4+
![Demo GIF](/doc/assets/demo.gif)
5+
6+
A lightweight React context that leverages [react-resize-detector](https://github.com/maslianok/react-resize-detector) to detect the current breakpoint based on an element's width. It provides utility functions and helper components to conditionally render content based on responsive breakpoints.
7+
8+
---
9+
10+
## Features
11+
12+
- Dynamically detects the current breakpoint based on element width.
13+
- Supports custom breakpoint names (e.g., "XS", "SM", "MD", "LG", "XL" or any arbitrary names like "Smart", "Mini", etc.).
14+
- Utility functions:
15+
- `isAtLeast(breakpoint)`: Returns `true` if the current breakpoint is greater than or equal to the provided one.
16+
- `isBelow(breakpoint)`: Returns `true` if the current breakpoint is less than the provided one.
17+
- `valueByBreakpoint(mapping)`: Returns a value from the provided mapping based on the current breakpoint.
18+
- Helper component `BreakpointConditional` for conditional rendering.
19+
- Error logging for invalid configurations (e.g. duplicate values, width not set, etc.).
20+
- Fully typed in TypeScript with comprehensive IDE support.
21+
22+
---
23+
24+
## Requirements
25+
26+
- **Node.js**: >= 18.0.0
27+
- **React**: >= 17
28+
- **React-DOM**: >= 17
29+
30+
---
31+
32+
## Installation
33+
34+
Install via npm:
35+
36+
```
37+
npm install my-breakpoint-package
38+
```
39+
40+
---
41+
42+
## Usage
43+
44+
### Basic Usage
45+
46+
Wrap your component tree with the `BreakpointProvider` and provide a breakpoint configuration. Then, access the current breakpoint and utility functions via the `useBreakpoint` hook.
47+
48+
```typescript
49+
import React from 'react';
50+
import { BreakpointProvider, useBreakpoint } from 'my-breakpoint-package';
51+
52+
const breakpoints = {
53+
XS: 0,
54+
SM: 500,
55+
MD: 700,
56+
LG: 900,
57+
XL: 1100,
58+
};
59+
60+
const ResponsiveComponent = () => {
61+
const { width, breakpoint, isAtLeast, valueByBreakpoint } = useBreakpoint();
62+
return (
63+
<div>
64+
<p>Current width: {width}px</p>
65+
<p>Current breakpoint: {breakpoint}</p>
66+
<p>Is at least MD: {isAtLeast('MD') ? '✅' : '❌'}</p>
67+
<p>
68+
Mapping: {valueByBreakpoint({
69+
XS: 'Extra Small',
70+
SM: 'Small',
71+
MD: 'Medium',
72+
LG: 'Large',
73+
XL: 'Extra Large'
74+
})}
75+
</p>
76+
</div>
77+
);
78+
};
79+
80+
const App = () => (
81+
<BreakpointProvider breakpoints={breakpoints}>
82+
<ResponsiveComponent />
83+
</BreakpointProvider>
84+
);
85+
86+
export default App;
87+
```
88+
89+
---
90+
91+
### Conditional Rendering with BreakpointConditional
92+
93+
Use `BreakpointConditional` to render content only when certain breakpoint conditions are met.
94+
95+
```typescript
96+
import React from 'react';
97+
import { BreakpointProvider, BreakpointConditional } from 'my-breakpoint-package';
98+
99+
const breakpoints = {
100+
XS: 0,
101+
SM: 500,
102+
MD: 700,
103+
LG: 900,
104+
XL: 1100,
105+
};
106+
107+
const ConditionalComponent = () => (
108+
<div>
109+
<BreakpointConditional isAtLeast="MD">
110+
<p>😊 This content is visible from MD and up.</p>
111+
</BreakpointConditional>
112+
<BreakpointConditional isBelow="LG">
113+
<p>🚀 This content is visible for breakpoints below LG.</p>
114+
</BreakpointConditional>
115+
</div>
116+
);
117+
118+
const App = () => (
119+
<BreakpointProvider breakpoints={breakpoints}>
120+
<ConditionalComponent />
121+
</BreakpointProvider>
122+
);
123+
124+
export default App;
125+
```
126+
127+
---
128+
129+
### Custom Breakpoints Example
130+
131+
You can define your own custom breakpoints with any names. For instance, using car sizes:
132+
133+
```typescript
134+
import React from 'react';
135+
import { BreakpointProvider, useBreakpoint } from 'my-breakpoint-package';
136+
137+
const carBreakpoints = {
138+
Smart: 0,
139+
Mini: 350,
140+
Sedan: 600,
141+
SUV: 900,
142+
Ram: 1200,
143+
};
144+
145+
const CarComponent = () => {
146+
const { width, breakpoint, valueByBreakpoint } = useBreakpoint();
147+
return (
148+
<div>
149+
<p>Current width: {width}px</p>
150+
<p>Current car size: {breakpoint}</p>
151+
<p>
152+
Mapping: {valueByBreakpoint({
153+
Smart: '🚗 Smart',
154+
Mini: '🚙 Mini',
155+
Sedan: '🚘 Sedan',
156+
SUV: '🚐 SUV',
157+
Ram: '🚚 Ram'
158+
})}
159+
</p>
160+
</div>
161+
);
162+
};
163+
164+
const App = () => (
165+
<BreakpointProvider breakpoints={carBreakpoints}>
166+
<CarComponent />
167+
</BreakpointProvider>
168+
);
169+
170+
export default App;
171+
```
172+
173+
---
174+
175+
## Available Scripts
176+
177+
You can use the following scripts from your command line:
178+
179+
- **`npm run build`**
180+
Builds the package (using [tsup](https://github.com/egoist/tsup)).
181+
182+
- **`npm run dev`**
183+
Runs development mode concurrently with build watch, Storybook, and tests.
184+
185+
- **`npm run storybook`**
186+
Launches Storybook for interactive component demos on port 6006.
187+
188+
- **`npm run test`**
189+
Runs tests using Vitest.
190+
191+
- **`npm run test:ci`**
192+
Runs tests with coverage.
193+
194+
- **`npm run lint`**
195+
Runs biome check with auto-fix.
196+
197+
- **`npm run release`**
198+
Builds and triggers the release process.
199+
200+
---
201+
202+
## License
203+
204+
This project is licensed under the MIT License.
205+
206+
---

tsconfig.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"esModuleInterop": true,
4+
"jsx": "react-jsx",
5+
"module": "ESNext",
6+
"moduleResolution": "Bundler",
7+
"skipLibCheck": true,
8+
"strict": true,
9+
"noEmit": true
10+
}
11+
}

tsup.config.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import childProcess from "node:child_process";
2+
import fs from "node:fs";
3+
import { readFile } from "node:fs/promises";
4+
import path from "node:path";
5+
import { type Options, defineConfig } from "tsup";
6+
7+
const common: Options = {
8+
entry: ["src/index.ts"],
9+
treeshake: false,
10+
sourcemap: "inline",
11+
minify: true,
12+
clean: true,
13+
dts: true,
14+
splitting: false,
15+
format: ["cjs", "esm"],
16+
external: ["react"],
17+
injectStyle: false,
18+
};
19+
20+
const getPackageName = async () => {
21+
try {
22+
const packageJson = JSON.parse(
23+
await readFile(path.join(__dirname, "package.json"), "utf-8"),
24+
);
25+
return packageJson.name;
26+
} catch (_error) {
27+
return "package-name";
28+
}
29+
};
30+
31+
const _addUseStatement = async (
32+
basePath: string,
33+
type: "server" | "client",
34+
) => {
35+
const fullPath = path.join(__dirname, basePath);
36+
const files = fs.readdirSync(fullPath);
37+
38+
for (const file of files) {
39+
if (file.endsWith(".js") || file.endsWith(".mjs")) {
40+
const filePath = path.join(fullPath, file);
41+
let content = await readFile(filePath, "utf-8");
42+
content = `"use ${type}";\n${content}`;
43+
fs.writeFileSync(filePath, content, "utf-8");
44+
}
45+
}
46+
};
47+
48+
const linkSelf = async () => {
49+
await new Promise((resolve) => {
50+
childProcess.exec("pnpm link:self", (error, _stdout, _stderr) => {
51+
if (error) {
52+
// biome-ignore lint/suspicious/noConsole: <explanation>
53+
console.error(`exec error: ${error}`);
54+
return;
55+
}
56+
57+
resolve(undefined);
58+
});
59+
});
60+
61+
// biome-ignore lint/suspicious/noConsoleLog: <explanation>
62+
// biome-ignore lint/suspicious/noConsole: <explanation>
63+
console.log(
64+
`Run 'pnpm link ${await getPackageName()} --global' inside another project to consume this package.`,
65+
);
66+
};
67+
68+
export default defineConfig({
69+
async onSuccess() {
70+
// If you want need to add a use statement to files, you can use the following code:
71+
// await _addUseStatement('dist/react', 'client');
72+
73+
await linkSelf();
74+
},
75+
...common,
76+
});

vitest.config.mts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
environment: "jsdom",
6+
setupFiles: "./tests/setup.js",
7+
passWithNoTests: true,
8+
coverage: {
9+
include: ["{src,tests}/**/*"],
10+
},
11+
},
12+
});

0 commit comments

Comments
 (0)