Skip to content

Commit 2adcae0

Browse files
committed
[release] 0.1.3 - Update package version, enhance README with usage instructions, and refactor BreakPointer component for improved key handling
1 parent 80ec655 commit 2adcae0

5 files changed

Lines changed: 254 additions & 203 deletions

File tree

README.md

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ A development utility React component that displays the current Tailwind CSS bre
1414
- **Live Breakpoint Display** - Shows current Tailwind breakpoint (xs, sm, md, lg, xl, 2xl)
1515
- **Viewport Dimensions** - Displays real-time width and height
1616
- **Color-Coded Indicators** - Each breakpoint has a unique color for quick identification
17-
- **Keyboard Toggle** - Press 't' (configurable) to show/hide
17+
- **Keyboard Toggle** - Visible by default; press 't' to turn on and 'Shift+T' to turn off (configurable)
1818
- **Production Safe** - Automatically hidden in production builds
1919
- **Zero Dependencies** - Only requires React as a peer dependency
2020
- **Highly Configurable** - Customize position, visibility, font, and more
@@ -96,30 +96,63 @@ function MyApp() {
9696
/>
9797
```
9898

99-
### Next.js App Router
99+
### Next.js 15 App Router
100+
101+
In App Router, render the component inside a Client Component. Importing from a Server Component resolves to a no-op by design.
102+
103+
Option A: Use an existing Client Component (recommended)
100104

101105
```tsx
102-
// app/layout.tsx
103-
import "@fontsource/jetbrains-mono/variable.css";
104-
import { BreakPointer } from "react-tw-breakpointer";
106+
// app/providers.tsx
107+
'use client';
108+
109+
import { BreakPointer } from 'react-tw-breakpointer';
110+
111+
export function Providers({ children }: { children: React.ReactNode }) {
112+
return (
113+
<>
114+
{children}
115+
<BreakPointer />
116+
</>
117+
);
118+
}
119+
```
120+
121+
Then include `<Providers>` in `app/layout.tsx`.
122+
123+
Option B: Minimal client wrapper if you don't have one yet
124+
125+
```tsx
126+
// app/components/BreakPointerClient.tsx
127+
'use client';
128+
129+
import { BreakPointer } from 'react-tw-breakpointer';
130+
131+
export function BreakPointerClient() {
132+
return <BreakPointer />;
133+
}
134+
```
135+
136+
```tsx
137+
// app/layout.tsx (server component)
138+
import type { ReactNode } from 'react';
139+
import { BreakPointerClient } from './components/BreakPointerClient';
105140

106-
export default function RootLayout({
107-
children,
108-
}: {
109-
children: React.ReactNode;
110-
}) {
141+
export default function RootLayout({ children }: { children: ReactNode }) {
111142
return (
112143
<html lang="en">
113144
<body>
114145
{children}
115-
{process.env.NODE_ENV === "development" && <BreakPointer />}
146+
<BreakPointerClient />
116147
</body>
117148
</html>
118149
);
119150
}
120151
```
121152

122-
**Note**: In App Router, `process.env.NODE_ENV` is available on the server, so the component is conditionally rendered server-side. This is more efficient than client-side conditional rendering since the component bundle isn't even sent to production users.
153+
Behavior:
154+
- In development, the component renders on the client.
155+
- In production and on the server, it resolves to a no-op.
123156

124157
### Next.js Pages Router
125158

@@ -161,7 +194,9 @@ function App() {
161194
| Prop | Type | Default | Description |
162195
| ------------------ | --------------------- | -------------------- | ----------------------------------------- |
163196
| `initiallyVisible` | `boolean` | `true` | Whether the component is visible on mount |
164-
| `toggleKey` | `string` | `'t'` | Keyboard key to toggle visibility |
197+
| `toggleKey` | `string` | `deprecated` | Deprecated; use `toggleOnKey`/`toggleOffKey` |
198+
| `toggleOnKey` | `string` | `'t'` | Keyboard key to turn the overlay on |
199+
| `toggleOffKey` | `string` | `'T'` | Keyboard key to turn the overlay off |
165200
| `position` | `Position` | `'bottom-center'` | Position of the overlay on screen |
166201
| `zIndex` | `number` | `9999` | z-index of the overlay |
167202
| `hideInProduction` | `boolean` | `true` | Automatically hide in production builds |
@@ -233,10 +268,10 @@ const config: BreakPointerProps = {
233268

234269
### Why isn't the component showing?
235270

236-
1. Check if you're in production mode (`NODE_ENV=production`)
237-
2. Try pressing the toggle key (default: 't')
238-
3. Ensure Tailwind CSS is properly configured
239-
4. Check z-index conflicts with other overlays
271+
1. Ensure it's rendered from a Client Component (App Router server files resolve to a no-op)
272+
2. Verify you're in development mode (`next dev`); production resolves to a no-op by default
273+
3. Try pressing the default keys: 't' turns on, 'Shift+T' turns off
274+
4. Ensure Tailwind CSS is properly configured and no z-index conflicts
240275

241276
### Can I use this without Tailwind CSS?
242277

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-tw-breakpointer",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"description": "A React component that displays the current Tailwind CSS breakpoint and viewport dimensions",
55
"type": "module",
66
"main": "dist/client.cjs",

src/BreakPointer.tsx

Lines changed: 1 addition & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,184 +1 @@
1-
import type React from 'react';
2-
import { useEffect, useState } from 'react';
3-
4-
export type Position =
5-
| 'bottom-center'
6-
| 'top-center'
7-
| 'top-left'
8-
| 'top-right'
9-
| 'bottom-left'
10-
| 'bottom-right';
11-
12-
export interface BreakPointerProps {
13-
initiallyVisible?: boolean;
14-
toggleKey?: string;
15-
position?: Position;
16-
zIndex?: number;
17-
hideInProduction?: boolean;
18-
showDimensions?: boolean;
19-
className?: string;
20-
style?: React.CSSProperties;
21-
fontFamily?: string;
22-
}
23-
24-
const DEFAULT_FONT_FAMILY =
25-
'JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace';
26-
27-
const positionClasses: Record<Position, string> = {
28-
'bottom-center': 'fixed bottom-2 left-1/2 -translate-x-1/2',
29-
'top-center': 'fixed top-2 left-1/2 -translate-x-1/2',
30-
'top-left': 'fixed top-2 left-2',
31-
'top-right': 'fixed top-2 right-2',
32-
'bottom-left': 'fixed bottom-2 left-2',
33-
'bottom-right': 'fixed bottom-2 right-2',
34-
};
35-
36-
export const BreakPointer: React.FC<BreakPointerProps> = ({
37-
initiallyVisible = true,
38-
toggleKey = 't',
39-
position = 'bottom-center',
40-
zIndex = 9999,
41-
hideInProduction = true,
42-
showDimensions = true,
43-
className = '',
44-
style = {},
45-
fontFamily = DEFAULT_FONT_FAMILY,
46-
}) => {
47-
const [isVisible, setIsVisible] = useState(initiallyVisible);
48-
const [viewport, setViewport] = useState({
49-
width: 0,
50-
height: 0,
51-
});
52-
const [isMounted, setIsMounted] = useState(false);
53-
54-
useEffect(() => {
55-
setIsMounted(true);
56-
57-
const updateViewport = () => {
58-
if (typeof window !== 'undefined') {
59-
setViewport({
60-
width: window.innerWidth,
61-
height: window.innerHeight,
62-
});
63-
}
64-
};
65-
66-
const handleKeyPress = (event: KeyboardEvent) => {
67-
if (event.key === toggleKey || event.key === toggleKey.toUpperCase()) {
68-
setIsVisible((prev) => !prev);
69-
}
70-
};
71-
72-
// Throttle resize events for better performance
73-
let resizeTimeout: NodeJS.Timeout;
74-
const throttledResize = () => {
75-
clearTimeout(resizeTimeout);
76-
resizeTimeout = setTimeout(updateViewport, 16); // ~60fps
77-
};
78-
79-
updateViewport();
80-
81-
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
82-
document.addEventListener('keydown', handleKeyPress);
83-
window.addEventListener('resize', throttledResize);
84-
}
85-
86-
return () => {
87-
clearTimeout(resizeTimeout);
88-
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
89-
document.removeEventListener('keydown', handleKeyPress);
90-
window.removeEventListener('resize', throttledResize);
91-
}
92-
};
93-
}, [toggleKey]);
94-
95-
// Early return for production builds
96-
// In Next.js App Router, this check happens server-side, preventing the component from being sent to production
97-
if (
98-
hideInProduction &&
99-
typeof process !== 'undefined' &&
100-
process.env?.NODE_ENV === 'production'
101-
) {
102-
return null;
103-
}
104-
105-
if (!isMounted) {
106-
return null;
107-
}
108-
109-
if (!isVisible) {
110-
return null;
111-
}
112-
113-
const positionClass = positionClasses[position] || positionClasses['bottom-center'];
114-
115-
const containerStyles: React.CSSProperties = {
116-
zIndex,
117-
fontFamily,
118-
...style,
119-
};
120-
121-
return (
122-
<div
123-
className={`${positionClass} rounded border-2 border-black text-xs ${className}`}
124-
style={containerStyles}
125-
aria-hidden="true"
126-
>
127-
<span className="block items-center bg-[#ec2427] px-2 py-1 font-mono font-semibold tracking-tighter text-white sm:hidden md:hidden lg:hidden xl:hidden 2xl:hidden">
128-
1/6 <span className="text-black"></span> xs <span className="text-black"></span>{' '}
129-
{showDimensions && (
130-
<span className="text-gray-100">
131-
{viewport.width}px <span className="text-black">&lt;</span> 640px
132-
</span>
133-
)}
134-
</span>
135-
136-
<span className="hidden items-center bg-[#f36525] px-2 py-1 font-mono font-semibold tracking-tighter text-white sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">
137-
2/6 <span className="text-black"></span> sm <span className="text-black"></span>{' '}
138-
{showDimensions && (
139-
<span className="text-gray-100">
140-
{viewport.width}px <span className="text-black">&lt;</span> 768px
141-
</span>
142-
)}
143-
</span>
144-
145-
<span className="hidden items-center bg-[#edb41f] px-2 py-1 font-mono font-semibold tracking-tighter text-white md:block lg:hidden xl:hidden 2xl:hidden">
146-
3/6 <span className="text-black"></span> md <span className="text-black"></span>{' '}
147-
{showDimensions && (
148-
<span className="text-gray-100">
149-
{viewport.width}px <span className="text-black">&lt;</span> 1024px
150-
</span>
151-
)}
152-
</span>
153-
154-
<span className="hidden items-center bg-[#f7ee49] px-2 py-1 font-mono font-semibold tracking-tighter text-black lg:block xl:hidden 2xl:hidden">
155-
4/6 <span className="text-black"></span> lg <span className="text-black"></span>{' '}
156-
{showDimensions && (
157-
<span className="text-black">
158-
{viewport.width}px <span className="text-black">&lt;</span> 1280px
159-
</span>
160-
)}
161-
</span>
162-
163-
<span className="hidden items-center bg-[#4686c5] px-2 py-1 font-mono font-semibold tracking-tighter text-white xl:block 2xl:hidden">
164-
5/6 <span className="text-black"></span> xl <span className="text-black"></span>{' '}
165-
{showDimensions && (
166-
<span className="text-gray-100">
167-
{viewport.width}px <span className="text-black">&lt;</span> 1536px
168-
</span>
169-
)}
170-
</span>
171-
172-
<span className="hidden items-center bg-[#45b64a] px-2 py-1 font-mono font-semibold tracking-tighter text-white 2xl:block">
173-
6/6 <span className="text-black"></span> 2xl <span className="text-black"></span>{' '}
174-
{showDimensions && (
175-
<span className="text-gray-100">
176-
{viewport.width}px <span className="text-black"></span> 1536px
177-
</span>
178-
)}
179-
</span>
180-
</div>
181-
);
182-
};
183-
184-
// Remove default export to avoid mixed exports warning
1+
export { BreakPointer, type BreakPointerProps, type Position } from './client';

0 commit comments

Comments
 (0)