overflow-guard-react 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Artur Marczyk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,354 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/arturmarc/overflow-guard/main/docs/assets/overflow-guard-logo.svg" alt="Overflow Guard logo" width="88" />
3
+ </p>
4
+
5
+ # overflow-guard-react
6
+
7
+ ![Overflow Guard demo](https://raw.githubusercontent.com/arturmarc/overflow-guard/main/docs/assets/overflow-guard-demo.gif)
8
+
9
+ `overflow-guard-react` helps React UI adapt when content stops fitting, instead of relying on viewport breakpoints, container breakpoints, or magic numbers.
10
+
11
+ Repository: <https://github.com/arturmarc/overflow-guard>
12
+ Website: <https://overflow-guard.vercel.app/>
13
+
14
+ Wrap a piece of UI in `<OverflowGuard>`, and it tells you when content no longer fits the available space. You can then switch to a compact layout, collapse actions to icons, swap a full nav for a menu, or reveal a "Read more" affordance.
15
+
16
+ ## Why use it
17
+
18
+ - Build around content, not guessed pixel values
19
+ - Handle dynamic labels, localization, and data-driven content
20
+ - Reuse the same component across narrow sidebars, wide panels, and resizable layouts
21
+ - Respond to horizontal overflow, vertical overflow, or both
22
+
23
+ ## Installation
24
+
25
+ ```sh
26
+ bun add overflow-guard-react
27
+ npm install overflow-guard-react
28
+ pnpm add overflow-guard-react
29
+ yarn add overflow-guard-react
30
+ ```
31
+
32
+ Peer dependencies:
33
+
34
+ - `react`
35
+ - `react-dom`
36
+
37
+ `overflow-guard-react` is a client-side package. It measures rendered DOM with `ResizeObserver`, so use it from client components in frameworks that distinguish server and client rendering.
38
+
39
+ ## Quick start
40
+
41
+ ```tsx
42
+ import { OverflowGuard } from 'overflow-guard-react'
43
+
44
+ export function AdaptiveToolbar() {
45
+ return (
46
+ <OverflowGuard>
47
+ {(isOverflowing) => (
48
+ <div className={isOverflowing ? 'flex flex-wrap gap-2' : 'flex gap-2'}>
49
+ <button>Search</button>
50
+ <button>Share update</button>
51
+ <button>Launch flow</button>
52
+ </div>
53
+ )}
54
+ </OverflowGuard>
55
+ )
56
+ }
57
+ ```
58
+
59
+ ## How it works
60
+
61
+ `OverflowGuard` renders your content into a hidden measurement layer and compares its `scrollWidth` and `scrollHeight` against the available `clientWidth` and `clientHeight`.
62
+
63
+ At runtime it exposes:
64
+
65
+ - `isOverflowing`: `true` when content overflows on any axis
66
+ - `overflowAxis`: `'none' | 'horizontal' | 'vertical' | 'both'`
67
+
68
+ ## Usage patterns
69
+
70
+ ### 1. Fallback mode
71
+
72
+ Use fallback mode when the compact state should be a different tree entirely.
73
+
74
+ ```tsx
75
+ import { OverflowGuard } from 'overflow-guard-react'
76
+ import { Menu } from 'lucide-react'
77
+
78
+ function Brand() {
79
+ return <div className="font-semibold">OverflowGuard</div>
80
+ }
81
+
82
+ export function ResponsiveNav() {
83
+ return (
84
+ <OverflowGuard
85
+ fallbackOn="horizontal"
86
+ fallback={
87
+ <nav className="flex items-center justify-between rounded-3xl border px-4 py-3">
88
+ <Brand />
89
+ <button aria-label="Open menu">
90
+ <Menu />
91
+ </button>
92
+ </nav>
93
+ }
94
+ >
95
+ <nav className="flex min-w-max items-center justify-between gap-3 rounded-3xl border px-4 py-3">
96
+ <Brand />
97
+ <div className="flex flex-nowrap gap-2">
98
+ <a href="/docs">Docs</a>
99
+ <a href="/recipes">Recipes</a>
100
+ <a href="/playground">Playground</a>
101
+ <a href="/pricing">Pricing</a>
102
+ </div>
103
+ </nav>
104
+ </OverflowGuard>
105
+ )
106
+ }
107
+ ```
108
+
109
+ ### 2. Render prop mode
110
+
111
+ Use the render prop when both states share most of the same structure and only behavior or presentation changes.
112
+
113
+ ```tsx
114
+ import { OverflowGuard } from 'overflow-guard-react'
115
+
116
+ function ActionButtons() {
117
+ return (
118
+ <>
119
+ <button>Search</button>
120
+ <button>Export brief</button>
121
+ <button>Share update</button>
122
+ <button>Launch flow</button>
123
+ </>
124
+ )
125
+ }
126
+
127
+ function ActionIcons() {
128
+ return (
129
+ <>
130
+ <button aria-label="Search">S</button>
131
+ <button aria-label="Export brief">E</button>
132
+ <button aria-label="Share update">U</button>
133
+ <button aria-label="Launch flow">L</button>
134
+ </>
135
+ )
136
+ }
137
+
138
+ export function ProjectActions() {
139
+ return (
140
+ <OverflowGuard>
141
+ {(isOverflowing, overflowAxis) => (
142
+ <section className="flex flex-col gap-3">
143
+ <div className="text-sm text-slate-500">overflow: {overflowAxis}</div>
144
+ <div className={isOverflowing ? 'flex flex-wrap gap-2' : 'flex gap-2'}>
145
+ <div className="min-w-max rounded-xl border px-3 py-2">
146
+ Sprint planning
147
+ </div>
148
+ <div className="flex gap-2">
149
+ {isOverflowing ? <ActionIcons /> : <ActionButtons />}
150
+ </div>
151
+ </div>
152
+ </section>
153
+ )}
154
+ </OverflowGuard>
155
+ )
156
+ }
157
+ ```
158
+
159
+ ### 3. Custom hook mode
160
+
161
+ Use `useOverflowGuard()` when nested children need access to the overflow state without threading props through every layer.
162
+
163
+ The hook only returns the boolean state. If you need axis details, keep using the render prop at the boundary.
164
+
165
+ ```tsx
166
+ import {
167
+ OverflowGuard,
168
+ useOverflowGuard,
169
+ } from 'overflow-guard-react'
170
+
171
+ function ToolbarSummary() {
172
+ const isOverflowing = useOverflowGuard()
173
+
174
+ return (
175
+ <span className="text-sm">
176
+ {isOverflowing ? 'Compact mode' : 'Expanded mode'}
177
+ </span>
178
+ )
179
+ }
180
+
181
+ function ToolbarActions() {
182
+ const isOverflowing = useOverflowGuard()
183
+
184
+ return isOverflowing ? (
185
+ <>
186
+ <button aria-label="Overview">O</button>
187
+ <button aria-label="Team">T</button>
188
+ <button aria-label="Docs">D</button>
189
+ <button aria-label="Support">S</button>
190
+ </>
191
+ ) : (
192
+ <>
193
+ <button>Overview</button>
194
+ <button>Team updates</button>
195
+ <button>Docs space</button>
196
+ <button>Support</button>
197
+ </>
198
+ )
199
+ }
200
+
201
+ export function NestedExample() {
202
+ return (
203
+ <OverflowGuard className="w-full">
204
+ {() => (
205
+ <div className="flex flex-col gap-3">
206
+ <ToolbarSummary />
207
+ <div className="flex min-w-0 gap-2">
208
+ <ToolbarActions />
209
+ </div>
210
+ </div>
211
+ )}
212
+ </OverflowGuard>
213
+ )
214
+ }
215
+ ```
216
+
217
+ ### 4. Vertical overflow example
218
+
219
+ Overflow handling is not limited to width. You can react to height constraints too.
220
+
221
+ ```tsx
222
+ import { OverflowGuard } from 'overflow-guard-react'
223
+
224
+ export function ReadMoreCard() {
225
+ return (
226
+ <OverflowGuard className="h-full" containerClassName="h-64">
227
+ {(isOverflowing, overflowAxis) => {
228
+ const showReadMore =
229
+ overflowAxis === 'vertical' || overflowAxis === 'both'
230
+
231
+ return (
232
+ <article className="flex h-full flex-col rounded-3xl border p-5">
233
+ <div className="mb-3">
234
+ <h2 className="font-semibold">Release notes draft</h2>
235
+ </div>
236
+ <p className={showReadMore ? 'max-h-24 overflow-hidden' : undefined}>
237
+ OverflowGuard can reveal a call to action when the content exceeds
238
+ the available height instead of letting the card blow up the
239
+ surrounding layout.
240
+ </p>
241
+ <div className="mt-4">
242
+ {showReadMore ? <button>Read more</button> : null}
243
+ </div>
244
+ </article>
245
+ )
246
+ }}
247
+ </OverflowGuard>
248
+ )
249
+ }
250
+ ```
251
+
252
+ ## API
253
+
254
+ ### `OverflowGuard`
255
+
256
+ `OverflowGuard` supports two mutually exclusive modes:
257
+
258
+ - fallback mode: pass regular `children` plus `fallback`
259
+ - render prop mode: pass a function as `children`
260
+
261
+ ### Common props
262
+
263
+ These props are available in both modes.
264
+
265
+ | Prop | Type | Default | Description |
266
+ | --- | --- | --- | --- |
267
+ | `children` | `ReactNode` or `(isOverflowing, overflowAxis) => ReactNode` | - | Regular children in fallback mode, or a render function in render-prop mode. |
268
+ | `className` | `string` | - | Applied to the inner measured and visible content box. |
269
+ | `style` | `CSSProperties` | - | Applied to the inner measured and visible content box. |
270
+ | `containerClassName` | `string` | - | Applied to the outer wrapper that hosts the measurement and visible layers. |
271
+ | `containerStyle` | `CSSProperties` | - | Applied to the outer wrapper. The component also sets `display: grid` and `position: relative`. |
272
+ | `throttleTime` | `number` | `0` | Debounce-like delay, in milliseconds, before re-running overflow checks after resize observer updates. |
273
+ | `...divProps` | `HTMLAttributes<HTMLDivElement>` | - | Standard div props such as `id`, `role`, `data-*`, and `aria-*`. These are forwarded to the inner content box. |
274
+
275
+ ### Fallback mode props
276
+
277
+ | Prop | Type | Default | Description |
278
+ | --- | --- | --- | --- |
279
+ | `fallback` | `ReactNode` | required | Rendered when overflow matches `fallbackOn`. |
280
+ | `fallbackOn` | `'horizontal' \| 'vertical' \| 'both'` | `'both'` | Controls which overflow axis activates the fallback. `'both'` means any overflow triggers it. |
281
+
282
+ Example:
283
+
284
+ ```tsx
285
+ <OverflowGuard fallback={<CompactNav />} fallbackOn="horizontal">
286
+ <FullNav />
287
+ </OverflowGuard>
288
+ ```
289
+
290
+ ### Render prop signature
291
+
292
+ ```ts
293
+ (isOverflowing: boolean, overflowAxis: OverflowAxis) => ReactNode
294
+ ```
295
+
296
+ Parameters:
297
+
298
+ | Parameter | Type | Description |
299
+ | --- | --- | --- |
300
+ | `isOverflowing` | `boolean` | `true` when content overflows on any axis. |
301
+ | `overflowAxis` | `'none' \| 'horizontal' \| 'vertical' \| 'both'` | The measured overflow direction. |
302
+
303
+ Example:
304
+
305
+ ```tsx
306
+ <OverflowGuard>
307
+ {(isOverflowing, overflowAxis) => (
308
+ <div data-overflow-axis={overflowAxis}>
309
+ {isOverflowing ? <CompactLayout /> : <FullLayout />}
310
+ </div>
311
+ )}
312
+ </OverflowGuard>
313
+ ```
314
+
315
+ ### `useOverflowGuard()`
316
+
317
+ ```ts
318
+ const isOverflowing = useOverflowGuard()
319
+ ```
320
+
321
+ Returns the nearest `OverflowGuard` boolean overflow state.
322
+
323
+ Use it inside descendants rendered by `OverflowGuard` when you want nested components to react to the current compact or expanded state.
324
+
325
+ ## Exported types
326
+
327
+ ```ts
328
+ import type {
329
+ FallbackOn,
330
+ OverflowAxis,
331
+ OverflowGuardProps,
332
+ } from 'overflow-guard-react'
333
+ ```
334
+
335
+ ## Notes and gotchas
336
+
337
+ - `fallback` and render-prop `children` are mutually exclusive.
338
+ - `fallbackOn` can only be used together with `fallback`.
339
+ - The hook returns only `boolean`. It does not expose the overflow axis.
340
+ - `className` and `style` apply to the measured content box, so layout-affecting styles should usually live there.
341
+ - The outer container always uses `display: grid` and `position: relative` so the visible layer can stack on top of the hidden measurement layer.
342
+ - Overflow detection uses a small `+1` tolerance to avoid noisy flips from sub-pixel measurement differences.
343
+ - The component includes loop protection for layouts that oscillate endlessly between states. In that case it locks into an overflowing state and warns in the console.
344
+
345
+ ## When to choose this over CSS queries
346
+
347
+ Use `overflow-guard-react` when the decision depends on whether the rendered content actually fits:
348
+
349
+ - action bars that collapse only when labels stop fitting
350
+ - navigation that turns into a menu based on content length
351
+ - translated UI where text length changes by locale
352
+ - cards that reveal "Read more" only when body copy exceeds available height
353
+
354
+ Use media or container queries when you already know the rule should be based on viewport or container size alone.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`react`),l=require(`use-resize-observer`);l=s(l);let u=require(`react/jsx-runtime`);function d(e,t){return e&&t?`both`:e?`horizontal`:t?`vertical`:`none`}function f(e,t){return t===`none`?!1:e===`both`?!0:t===e||t===`both`}function p(e,t){let n=null;return(...r)=>{n&&clearTimeout(n),n=setTimeout(()=>{e(...r)},t)}}var m=0,h=300,g=7,_=(0,c.createContext)({isOverflowing:!1,overflowAxis:`none`});function v(){return(0,c.useContext)(_).isOverflowing}function y(e){let{children:t,className:n,containerClassName:r,containerStyle:i,style:a,throttleTime:o=m}=e,s=(0,c.useRef)(null),[v,y]=(0,c.useState)(`none`),[x,S]=(0,c.useState)(!1),C=b(e),w=(0,c.useCallback)(()=>{let e=s.current;if(!e)return;let t=e.scrollWidth>e.clientWidth+1,n=e.scrollHeight>e.clientHeight+1;y(e=>{let r=d(t,n);return e===r?e:r})},[]),T=(0,c.useCallback)(()=>{requestAnimationFrame(w)},[w]),E=(0,c.useMemo)(()=>p(()=>{T()},o),[T,o]),{ref:D}=(0,l.default)({onResize:E}),{ref:O}=(0,l.default)({onResize:E}),k=(0,c.useCallback)(e=>{s.current=e,D(e),T()},[D,T]);(0,c.useLayoutEffect)(()=>{w()});let A=(0,c.useRef)(0),j=(0,c.useRef)(0);(0,c.useEffect)(()=>{let e=Date.now();if(j.current===0&&(j.current=e),e-j.current>h){A.current=1,j.current=e,S(!1);return}A.current+=1,A.current>g&&(console.warn(`OverflowGuard infinite loop detected. The component will stay in the overflowing state to stabilize rendering.`),S(!0),y(e=>e===`none`?`both`:e))});let M=x&&v===`none`?`both`:v,N=M!==`none`,P=typeof t==`function`?t(!1,`none`):t,F=`fallback`in e?e.fallbackOn??`both`:`both`,I=`fallback`in e&&f(F,M),L=typeof t==`function`?t(N,M):I?e.fallback:t,R={...a,minWidth:0,minHeight:0};return(0,u.jsxs)(`div`,{ref:O,"data-overflow-guard":`container`,className:r,style:{...i,display:`grid`,position:`relative`},children:[(0,u.jsx)(`div`,{"aria-hidden":`true`,"data-overflow-guard":`hidden-measurement-layer`,style:{position:`absolute`,inset:0,overflow:`auto`,pointerEvents:`none`,visibility:`hidden`,zIndex:-1},children:(0,u.jsx)(`div`,{...C,ref:k,"data-overflow-guard":`hidden-measurement-box`,className:n,style:R,children:(0,u.jsx)(_.Provider,{value:{isOverflowing:!1,overflowAxis:`none`},children:P})})}),(0,u.jsx)(`div`,{"data-overflow-guard":`visible-layer-${M}`,style:{gridArea:`1 / 1`,minWidth:0,minHeight:0},children:(0,u.jsx)(`div`,{...C,className:n,style:R,"data-overflow-guard":`visible-box`,children:(0,u.jsx)(_.Provider,{value:{isOverflowing:N,overflowAxis:M},children:L})})})]})}function b(e){let t={...e};return delete t.children,delete t.className,delete t.containerClassName,delete t.containerStyle,delete t.fallback,delete t.fallbackOn,delete t.style,delete t.throttleTime,t}exports.OverflowGuard=y,exports.useOverflowGuard=v;
@@ -0,0 +1,2 @@
1
+ export { OverflowGuard, useOverflowGuard, } from './overflow-guard';
2
+ export type { FallbackOn, OverflowAxis, OverflowGuardProps, } from './overflow-guard';
package/dist/index.js ADDED
@@ -0,0 +1,124 @@
1
+ "use client";
2
+ import { createContext as e, useCallback as t, useContext as n, useEffect as r, useLayoutEffect as i, useMemo as a, useRef as o, useState as s } from "react";
3
+ import c from "use-resize-observer";
4
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
5
+ //#region src/utils.ts
6
+ function d(e, t) {
7
+ return e && t ? "both" : e ? "horizontal" : t ? "vertical" : "none";
8
+ }
9
+ function f(e, t) {
10
+ return t === "none" ? !1 : e === "both" ? !0 : t === e || t === "both";
11
+ }
12
+ function p(e, t) {
13
+ let n = null;
14
+ return (...r) => {
15
+ n && clearTimeout(n), n = setTimeout(() => {
16
+ e(...r);
17
+ }, t);
18
+ };
19
+ }
20
+ //#endregion
21
+ //#region src/overflow-guard.tsx
22
+ var m = 0, h = 300, g = 7, _ = e({
23
+ isOverflowing: !1,
24
+ overflowAxis: "none"
25
+ });
26
+ function v() {
27
+ return n(_).isOverflowing;
28
+ }
29
+ function y(e) {
30
+ let { children: n, className: v, containerClassName: y, containerStyle: x, style: S, throttleTime: C = m } = e, w = o(null), [T, E] = s("none"), [D, O] = s(!1), k = b(e), A = t(() => {
31
+ let e = w.current;
32
+ if (!e) return;
33
+ let t = e.scrollWidth > e.clientWidth + 1, n = e.scrollHeight > e.clientHeight + 1;
34
+ E((e) => {
35
+ let r = d(t, n);
36
+ return e === r ? e : r;
37
+ });
38
+ }, []), j = t(() => {
39
+ requestAnimationFrame(A);
40
+ }, [A]), M = a(() => p(() => {
41
+ j();
42
+ }, C), [j, C]), { ref: N } = c({ onResize: M }), { ref: P } = c({ onResize: M }), F = t((e) => {
43
+ w.current = e, N(e), j();
44
+ }, [N, j]);
45
+ i(() => {
46
+ A();
47
+ });
48
+ let I = o(0), L = o(0);
49
+ r(() => {
50
+ let e = Date.now();
51
+ if (L.current === 0 && (L.current = e), e - L.current > h) {
52
+ I.current = 1, L.current = e, O(!1);
53
+ return;
54
+ }
55
+ I.current += 1, I.current > g && (console.warn("OverflowGuard infinite loop detected. The component will stay in the overflowing state to stabilize rendering."), O(!0), E((e) => e === "none" ? "both" : e));
56
+ });
57
+ let R = D && T === "none" ? "both" : T, z = R !== "none", B = typeof n == "function" ? n(!1, "none") : n, V = "fallback" in e ? e.fallbackOn ?? "both" : "both", H = "fallback" in e && f(V, R), U = typeof n == "function" ? n(z, R) : H ? e.fallback : n, W = {
58
+ ...S,
59
+ minWidth: 0,
60
+ minHeight: 0
61
+ };
62
+ return /* @__PURE__ */ u("div", {
63
+ ref: P,
64
+ "data-overflow-guard": "container",
65
+ className: y,
66
+ style: {
67
+ ...x,
68
+ display: "grid",
69
+ position: "relative"
70
+ },
71
+ children: [/* @__PURE__ */ l("div", {
72
+ "aria-hidden": "true",
73
+ "data-overflow-guard": "hidden-measurement-layer",
74
+ style: {
75
+ position: "absolute",
76
+ inset: 0,
77
+ overflow: "auto",
78
+ pointerEvents: "none",
79
+ visibility: "hidden",
80
+ zIndex: -1
81
+ },
82
+ children: /* @__PURE__ */ l("div", {
83
+ ...k,
84
+ ref: F,
85
+ "data-overflow-guard": "hidden-measurement-box",
86
+ className: v,
87
+ style: W,
88
+ children: /* @__PURE__ */ l(_.Provider, {
89
+ value: {
90
+ isOverflowing: !1,
91
+ overflowAxis: "none"
92
+ },
93
+ children: B
94
+ })
95
+ })
96
+ }), /* @__PURE__ */ l("div", {
97
+ "data-overflow-guard": `visible-layer-${R}`,
98
+ style: {
99
+ gridArea: "1 / 1",
100
+ minWidth: 0,
101
+ minHeight: 0
102
+ },
103
+ children: /* @__PURE__ */ l("div", {
104
+ ...k,
105
+ className: v,
106
+ style: W,
107
+ "data-overflow-guard": "visible-box",
108
+ children: /* @__PURE__ */ l(_.Provider, {
109
+ value: {
110
+ isOverflowing: z,
111
+ overflowAxis: R
112
+ },
113
+ children: U
114
+ })
115
+ })
116
+ })]
117
+ });
118
+ }
119
+ function b(e) {
120
+ let t = { ...e };
121
+ return delete t.children, delete t.className, delete t.containerClassName, delete t.containerStyle, delete t.fallback, delete t.fallbackOn, delete t.style, delete t.throttleTime, t;
122
+ }
123
+ //#endregion
124
+ export { y as OverflowGuard, v as useOverflowGuard };
@@ -0,0 +1,23 @@
1
+ import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
2
+ export type OverflowAxis = 'none' | 'horizontal' | 'vertical' | 'both';
3
+ export type FallbackOn = Exclude<OverflowAxis, 'none'>;
4
+ type SharedProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
5
+ containerClassName?: string;
6
+ containerStyle?: CSSProperties;
7
+ throttleTime?: number;
8
+ };
9
+ type RenderProp = (isOverflowing: boolean, overflowAxis: OverflowAxis) => ReactNode;
10
+ type FallbackModeProps = SharedProps & {
11
+ children?: ReactNode;
12
+ fallback: ReactNode;
13
+ fallbackOn?: FallbackOn;
14
+ };
15
+ type RenderPropModeProps = SharedProps & {
16
+ children?: RenderProp;
17
+ fallback?: never;
18
+ fallbackOn?: never;
19
+ };
20
+ export type OverflowGuardProps = FallbackModeProps | RenderPropModeProps;
21
+ export declare function useOverflowGuard(): boolean;
22
+ export declare function OverflowGuard(props: OverflowGuardProps): import("react/jsx-runtime").JSX.Element;
23
+ export {};
@@ -0,0 +1,6 @@
1
+ type OverflowAxisLike = "none" | "horizontal" | "vertical" | "both";
2
+ type FallbackOnLike = Exclude<OverflowAxisLike, "none">;
3
+ export declare function resolveOverflowAxis(horizontalOverflow: boolean, verticalOverflow: boolean): OverflowAxisLike;
4
+ export declare function shouldUseFallback(fallbackOn: FallbackOnLike, overflowAxis: OverflowAxisLike): boolean;
5
+ export declare function throttle<T extends (...args: unknown[]) => void>(fn: T, delay: number): (...args: Parameters<T>) => void;
6
+ export {};
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "overflow-guard-react",
3
+ "version": "0.0.1",
4
+ "description": "React component and hook for content-aware responsive UI that adapts when content stops fitting, without breakpoints or magic numbers.",
5
+ "license": "MIT",
6
+ "author": "Artur Marczyk",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/arturmarc/overflow-guard.git",
10
+ "directory": "packages/overflow-guard-react"
11
+ },
12
+ "homepage": "https://github.com/arturmarc/overflow-guard/tree/main/packages/overflow-guard-react#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/arturmarc/overflow-guard/issues"
15
+ },
16
+ "keywords": [
17
+ "react",
18
+ "overflow",
19
+ "responsive",
20
+ "content-aware",
21
+ "content-aware-ui",
22
+ "responsive-ui",
23
+ "adaptive-ui",
24
+ "layout",
25
+ "toolbar",
26
+ "navigation",
27
+ "menu",
28
+ "dynamic-content",
29
+ "localization",
30
+ "resize-observer",
31
+ "render-props",
32
+ "hook",
33
+ "truncate"
34
+ ],
35
+ "type": "module",
36
+ "main": "./dist/index.cjs",
37
+ "module": "./dist/index.js",
38
+ "types": "./dist/index.d.ts",
39
+ "sideEffects": false,
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "exports": {
44
+ ".": {
45
+ "types": "./dist/index.d.ts",
46
+ "import": "./dist/index.js",
47
+ "require": "./dist/index.cjs"
48
+ }
49
+ },
50
+ "files": [
51
+ "dist",
52
+ "README.md",
53
+ "LICENSE"
54
+ ],
55
+ "scripts": {
56
+ "build": "vite build && tsc -p tsconfig.build.json",
57
+ "test": "vitest run",
58
+ "typecheck": "tsc -p tsconfig.json --noEmit"
59
+ },
60
+ "dependencies": {
61
+ "use-resize-observer": "^9.1.0"
62
+ },
63
+ "peerDependencies": {
64
+ "react": ">=16.8.0",
65
+ "react-dom": ">=16.8.0"
66
+ },
67
+ "devDependencies": {
68
+ "@testing-library/jest-dom": "^6.9.1",
69
+ "@testing-library/react": "^16.3.2",
70
+ "@types/node": "^24.10.1",
71
+ "@types/react": "^19.2.7",
72
+ "@types/react-dom": "^19.2.3",
73
+ "@vitejs/plugin-react": "^5.1.0",
74
+ "jsdom": "^28.1.0",
75
+ "react": "^19.2.0",
76
+ "react-dom": "^19.2.0",
77
+ "typescript": "~5.9.3",
78
+ "vite": "^8.0.0-beta.13",
79
+ "vitest": "^4.0.18"
80
+ }
81
+ }