react-overflow-tabs 0.1.0 → 1.1.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/README.md +12 -8
- package/dist/index.cjs +1 -0
- package/dist/{index.d.mts → index.d.cts} +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +1 -1
- package/package.json +20 -9
- package/dist/index.mjs +0 -1
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
A headless React hook to gracefully collapse overflowing tabs into a dropdown menu.
|
|
4
4
|
Works with any UI framework (Tailwind, Bootstrap, MUI, custom CSS).
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/react-overflow-tabs)
|
|
6
|
+
[](https://www.npmjs.com/package/react-overflow-tabs)
|
|
7
|
+
[](https://bundlephobia.com/package/react-overflow-tabs)
|
|
7
8
|
[](LICENSE)
|
|
8
9
|
|
|
9
10
|
A lightweight React hook for responsive tab navigation with automatic overflow handling.
|
|
@@ -128,18 +129,18 @@ const Example = () => {
|
|
|
128
129
|
|
|
129
130
|
#### Options (`IOverflowTabsOptions`)
|
|
130
131
|
|
|
131
|
-
| Option | Type | Default
|
|
132
|
-
| ------------- | ----------------------------------------- |
|
|
133
|
-
| `container` | `RefObject<HTMLElement>` \| `HTMLElement` | **required**
|
|
134
|
-
| `tabSelector` | `string` | `"
|
|
135
|
-
| `disabled` | `boolean` | `false`
|
|
132
|
+
| Option | Type | Default | Description |
|
|
133
|
+
| ------------- | ----------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------- |
|
|
134
|
+
| `container` | `RefObject<HTMLElement>` \| `HTMLElement` | **required** | The container element to observe. |
|
|
135
|
+
| `tabSelector` | `string` | `"data-overflow-key"` | Attribute that marks tabs (`"data-tab"` or `"[data-tab]"`). Its unique value is the tab key. |
|
|
136
|
+
| `disabled` | `boolean` | `false` | Temporarily disable overflow tracking; all tabs are reported as visible. |
|
|
136
137
|
|
|
137
138
|
#### Return (`IOverflowState`)
|
|
138
139
|
|
|
139
140
|
| Key | Type | Description |
|
|
140
141
|
| --------------- | ---------- | ------------------------------------------------ |
|
|
141
142
|
| `visibleKeys` | `string[]` | Keys of tabs currently visible in the container. |
|
|
142
|
-
| `overflowKeys` | `string[]` | Keys of tabs pushed into overflow
|
|
143
|
+
| `overflowKeys` | `string[]` | Keys of tabs pushed into overflow, in DOM order. |
|
|
143
144
|
| `isOverflowing` | `boolean` | Whether overflow is currently happening. |
|
|
144
145
|
|
|
145
146
|
---
|
|
@@ -148,7 +149,7 @@ const Example = () => {
|
|
|
148
149
|
|
|
149
150
|
```tsx
|
|
150
151
|
import { useRef } from "react";
|
|
151
|
-
import useOverflowTabs from "
|
|
152
|
+
import { useOverflowTabs } from "react-overflow-tabs";
|
|
152
153
|
|
|
153
154
|
const Example = () => {
|
|
154
155
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -207,6 +208,9 @@ const Example = () => {
|
|
|
207
208
|
- Uses **`IntersectionObserver`** under the hood → supported in modern browsers.
|
|
208
209
|
- `threshold: 0.999` by default → requires _full visibility_ to count as visible.
|
|
209
210
|
- Works with **RTL** layouts, as it only tracks visibility.
|
|
211
|
+
- Tabs added, removed or re-keyed after mount are picked up automatically (via `MutationObserver`).
|
|
212
|
+
- `tabSelector` must be an attribute name; CSS selectors such as `.tab` are rejected with an error.
|
|
213
|
+
- Without `IntersectionObserver` (e.g. SSR or jsdom), all tabs are reported as visible.
|
|
210
214
|
|
|
211
215
|
---
|
|
212
216
|
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var m=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var M=(t,e)=>{for(var s in e)m(t,s,{get:e[s],enumerable:!0})},H=(t,e,s,f)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of A(e))!K.call(t,a)&&a!==s&&m(t,a,{get:()=>e[a],enumerable:!(f=x(e,a))||f.enumerable});return t};var _=t=>H(m({},"__esModule",{value:!0}),t);var $={};M($,{useOverflowTabs:()=>g});module.exports=_($);var c=require("react");var k=t=>t?"current"in t?t.current:t:null,I=k;var R=/^-?[A-Za-z_][\w-]*$/,z=(t="data-overflow-key")=>{let e=t.trim();if(e.startsWith("[")&&e.endsWith("]")&&(e=e.slice(1,-1).trim()),!R.test(e))throw new Error(`react-overflow-tabs: invalid tabSelector "${t}". Expected an attribute name such as "data-overflow-key" or "[data-overflow-key]".`);return e},d=z;var O=.999,E={visibleKeys:[],overflowKeys:[],isOverflowing:!1},h=(t,e)=>t.length===e.length&&t.every((s,f)=>s===e[f]),C=(t,e)=>h(t.visibleKeys,e.visibleKeys)&&h(t.overflowKeys,e.overflowKeys),N=({container:t,tabSelector:e,disabled:s=!1})=>{let[f,a]=(0,c.useState)(E),[u,L]=(0,c.useState)(null),T=d(e);return(0,c.useEffect)(()=>{L(I(t))}),(0,c.useEffect)(()=>{let p=r=>a(o=>C(o,r)?o:r);if(!u){p(E);return}let w=[],v=new Set,y=()=>{let r=[],o=[];for(let b of w){let l=b.getAttribute(T);l&&(v.has(b)?o:r).push(l)}p({visibleKeys:r,overflowKeys:o,isOverflowing:o.length>0})},n=s||typeof IntersectionObserver=="undefined"?null:new IntersectionObserver(r=>{for(let o of r)o.intersectionRatio<O?v.add(o.target):v.delete(o.target);y()},{root:u,threshold:O}),S=()=>{let r=Array.from(u.querySelectorAll(`[${T}]`)),o=new Set(r),b=new Set(w);for(let l of w)o.has(l)||(n==null||n.unobserve(l),v.delete(l));for(let l of r)b.has(l)||n==null||n.observe(l);w=r,y()};S();let i=typeof MutationObserver=="undefined"?null:new MutationObserver(S);return i==null||i.observe(u,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[T]}),()=>{n==null||n.disconnect(),i==null||i.disconnect()}},[u,T,s]),f},g=N;0&&(module.exports={useOverflowTabs});
|
|
@@ -9,11 +9,15 @@ interface IOverflowTabsOptions<T extends HTMLElement = HTMLElement> {
|
|
|
9
9
|
*/
|
|
10
10
|
container: RefObject<T | null> | T;
|
|
11
11
|
/**
|
|
12
|
-
* Attribute name
|
|
13
|
-
* Each tab must have this attribute with a unique value.
|
|
14
|
-
* Default: "data-overflow-key"
|
|
12
|
+
* Attribute name that marks tabs, with or without square brackets.
|
|
13
|
+
* Each tab must have this attribute with a unique value, which is reported as its key.
|
|
14
|
+
* Default: "data-overflow-key"
|
|
15
15
|
*/
|
|
16
16
|
tabSelector?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Temporarily disable overflow tracking; all tabs are reported as visible.
|
|
19
|
+
* Default: false
|
|
20
|
+
*/
|
|
17
21
|
disabled?: boolean;
|
|
18
22
|
}
|
|
19
23
|
interface IOverflowState {
|
|
@@ -22,6 +26,6 @@ interface IOverflowState {
|
|
|
22
26
|
isOverflowing: boolean;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
declare const useOverflowTabs: <T extends HTMLElement = HTMLElement>(
|
|
29
|
+
declare const useOverflowTabs: <T extends HTMLElement = HTMLElement>({ container, tabSelector, disabled, }: IOverflowTabsOptions<T>) => IOverflowState;
|
|
26
30
|
|
|
27
31
|
export { type IOverflowState, type IOverflowTabsOptions, type TOverflowDirection, useOverflowTabs };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,11 +9,15 @@ interface IOverflowTabsOptions<T extends HTMLElement = HTMLElement> {
|
|
|
9
9
|
*/
|
|
10
10
|
container: RefObject<T | null> | T;
|
|
11
11
|
/**
|
|
12
|
-
* Attribute name
|
|
13
|
-
* Each tab must have this attribute with a unique value.
|
|
14
|
-
* Default: "data-overflow-key"
|
|
12
|
+
* Attribute name that marks tabs, with or without square brackets.
|
|
13
|
+
* Each tab must have this attribute with a unique value, which is reported as its key.
|
|
14
|
+
* Default: "data-overflow-key"
|
|
15
15
|
*/
|
|
16
16
|
tabSelector?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Temporarily disable overflow tracking; all tabs are reported as visible.
|
|
19
|
+
* Default: false
|
|
20
|
+
*/
|
|
17
21
|
disabled?: boolean;
|
|
18
22
|
}
|
|
19
23
|
interface IOverflowState {
|
|
@@ -22,6 +26,6 @@ interface IOverflowState {
|
|
|
22
26
|
isOverflowing: boolean;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
declare const useOverflowTabs: <T extends HTMLElement = HTMLElement>(
|
|
29
|
+
declare const useOverflowTabs: <T extends HTMLElement = HTMLElement>({ container, tabSelector, disabled, }: IOverflowTabsOptions<T>) => IOverflowState;
|
|
26
30
|
|
|
27
31
|
export { type IOverflowState, type IOverflowTabsOptions, type TOverflowDirection, useOverflowTabs };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{useEffect as S,useState as I}from"react";var L=e=>e?"current"in e?e.current:e:null,p=L;var x=/^-?[A-Za-z_][\w-]*$/,A=(e="data-overflow-key")=>{let t=e.trim();if(t.startsWith("[")&&t.endsWith("]")&&(t=t.slice(1,-1).trim()),!x.test(t))throw new Error(`react-overflow-tabs: invalid tabSelector "${e}". Expected an attribute name such as "data-overflow-key" or "[data-overflow-key]".`);return t},y=A;var d=.999,O={visibleKeys:[],overflowKeys:[],isOverflowing:!1},E=(e,t)=>e.length===t.length&&e.every((f,w)=>f===t[w]),K=(e,t)=>E(e.visibleKeys,t.visibleKeys)&&E(e.overflowKeys,t.overflowKeys),M=({container:e,tabSelector:t,disabled:f=!1})=>{let[w,h]=I(O),[a,g]=I(null),i=y(t);return S(()=>{g(p(e))}),S(()=>{let v=r=>h(o=>K(o,r)?o:r);if(!a){v(O);return}let c=[],u=new Set,b=()=>{let r=[],o=[];for(let T of c){let n=T.getAttribute(i);n&&(u.has(T)?o:r).push(n)}v({visibleKeys:r,overflowKeys:o,isOverflowing:o.length>0})},s=f||typeof IntersectionObserver=="undefined"?null:new IntersectionObserver(r=>{for(let o of r)o.intersectionRatio<d?u.add(o.target):u.delete(o.target);b()},{root:a,threshold:d}),m=()=>{let r=Array.from(a.querySelectorAll(`[${i}]`)),o=new Set(r),T=new Set(c);for(let n of c)o.has(n)||(s==null||s.unobserve(n),u.delete(n));for(let n of r)T.has(n)||s==null||s.observe(n);c=r,b()};m();let l=typeof MutationObserver=="undefined"?null:new MutationObserver(m);return l==null||l.observe(a,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[i]}),()=>{s==null||s.disconnect(),l==null||l.disconnect()}},[a,i,f]),w},H=M;export{H as useOverflowTabs};
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-overflow-tabs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "A lightweight React hook for responsive tab navigation with automatic overflow handling. Works with any UI framework (Tailwind, Bootstrap, MUI, custom CSS).",
|
|
5
|
-
"main": "dist/index.
|
|
6
|
-
"module": "dist/index.
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
7
8
|
"types": "dist/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.
|
|
12
|
-
"require": "./dist/index.
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
13
14
|
},
|
|
14
15
|
"./package.json": "./package.json"
|
|
15
16
|
},
|
|
@@ -19,7 +20,11 @@
|
|
|
19
20
|
"sideEffects": false,
|
|
20
21
|
"scripts": {
|
|
21
22
|
"build": "tsup",
|
|
22
|
-
"format": "prettier --write
|
|
23
|
+
"format": "prettier --write .",
|
|
24
|
+
"format:check": "prettier --check .",
|
|
25
|
+
"lint": "eslint .",
|
|
26
|
+
"lint:fix": "eslint . --fix",
|
|
27
|
+
"test": "vitest run",
|
|
23
28
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
24
29
|
},
|
|
25
30
|
"repository": {
|
|
@@ -61,17 +66,23 @@
|
|
|
61
66
|
"react": ">=18 <20"
|
|
62
67
|
},
|
|
63
68
|
"devDependencies": {
|
|
69
|
+
"@eslint/js": "^9.34.0",
|
|
64
70
|
"@semantic-release/changelog": "^6.0.3",
|
|
65
71
|
"@semantic-release/git": "^10.0.1",
|
|
66
|
-
"@
|
|
67
|
-
"@semantic-release/npm": "^12.0.2",
|
|
72
|
+
"@testing-library/react": "^16.3.3",
|
|
68
73
|
"@types/react": "^19.1.10",
|
|
69
74
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
75
|
+
"eslint": "^9.34.0",
|
|
76
|
+
"globals": "^17.12.0",
|
|
77
|
+
"jsdom": "^29.1.1",
|
|
70
78
|
"prettier": "^3.6.2",
|
|
71
79
|
"react": "^19.1.1",
|
|
80
|
+
"react-dom": "^19.1.1",
|
|
72
81
|
"semantic-release": "^24.2.7",
|
|
73
82
|
"tsup": "^8.5.0",
|
|
74
|
-
"typescript": "^5.9.2"
|
|
83
|
+
"typescript": "^5.9.2",
|
|
84
|
+
"typescript-eslint": "^8.41.0",
|
|
85
|
+
"vitest": "^4.1.11"
|
|
75
86
|
},
|
|
76
87
|
"engines": {
|
|
77
88
|
"node": ">=18"
|
package/dist/index.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{useEffect as E,useState as f}from"react";var g=e=>e?typeof e=="object"&&"current"in e?e.current:e:null,T=g;var S=e=>{let t=e!=null?e:"[data-overflow-key]";return t.startsWith("[")&&t.endsWith("]")?t.slice(1,-1):t},d=S;var I=e=>{let[t,a]=f([]),[y,c]=f([]),[p,m]=f(!1);return E(()=>{let l=T(e.container);if(!l)return;let i=d(e.tabSelector),u=Array.from(l.querySelectorAll(`[${i}]`));if(a(u.map(s=>s.getAttribute(i))),e.disabled==!0){c([]),m(!1);return}let b=new Set,n=new Set,v=new IntersectionObserver(s=>{let O=!1;for(let r of s){let o=r.target.getAttribute(i);if(!o)continue;b.add(o);let w=n.size;r.intersectionRatio<1?n.add(o):n.delete(o),n.size!=w&&(O=!0)}if(O){let r=Array.from(n);c(r.reverse()),a(Array.from(b).filter(o=>!r.includes(o))),m(r.length>0)}},{root:l,threshold:.999});return u.forEach(s=>v.observe(s)),()=>{v.disconnect()}},[e.container,e.tabSelector,e.disabled]),{visibleKeys:t,overflowKeys:y,isOverflowing:p}},K=I;export{K as useOverflowTabs};
|