@entro314labs/react-arc-tabs 1.0.0 → 1.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 +21 -0
- package/README.md +59 -112
- package/dist/ArcTabs.css +10 -13
- package/dist/index.cjs +55 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +55 -52
- package/dist/index.js.map +1 -1
- package/package.json +41 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 entro314-labs
|
|
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
CHANGED
|
@@ -1,43 +1,32 @@
|
|
|
1
|
-
# react-arc-tabs
|
|
1
|
+
# @entro314labs/react-arc-tabs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reusable arc-style tabs component for React and Next.js, including a Tailwind CSS v4+ variant.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Highlights
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
- Accessible tabs: ARIA roles + keyboard interactions
|
|
8
|
+
- Controlled and uncontrolled state models
|
|
9
|
+
- CSS-based and Tailwind v4+ variants
|
|
10
|
+
- Switch animations (including sliding active backdrop and animated panel entry)
|
|
11
|
+
- TypeScript-first API
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## Compatibility
|
|
13
14
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- Manual or automatic activation mode
|
|
18
|
-
- Keep mounted or render-only-active panel strategies
|
|
19
|
-
- Themeable with CSS variables and props
|
|
20
|
-
- Two styling engines:
|
|
21
|
-
- `ArcTabs` (CSS file based)
|
|
22
|
-
- `ArcTabsTailwind` (Tailwind CSS v4+ utility based)
|
|
23
|
-
- Built-in tab switch animations adapted from the demos:
|
|
24
|
-
- sliding active backdrop (`rounded-tab` inspired)
|
|
25
|
-
- smooth seam/corner transitions (`inverse rounded corners` inspired)
|
|
26
|
-
- animated panel entry with directional motion
|
|
27
|
-
- Works in React and Next.js (`"use client"` included)
|
|
15
|
+
- React `18.2+` and `19+`
|
|
16
|
+
- Next.js (App Router and Pages Router)
|
|
17
|
+
- Tailwind CSS `4+` for `ArcTabsTailwind`
|
|
28
18
|
|
|
29
|
-
## Install
|
|
19
|
+
## Install
|
|
30
20
|
|
|
31
21
|
```bash
|
|
32
|
-
npm install
|
|
33
|
-
npm run build
|
|
22
|
+
npm install @entro314labs/react-arc-tabs
|
|
34
23
|
```
|
|
35
24
|
|
|
36
|
-
##
|
|
25
|
+
## Basic usage (CSS variant)
|
|
37
26
|
|
|
38
27
|
```tsx
|
|
39
|
-
import { ArcTabs, type ArcTabItem } from "react-arc-tabs";
|
|
40
|
-
import "react-arc-tabs/styles.css";
|
|
28
|
+
import { ArcTabs, type ArcTabItem } from "@entro314labs/react-arc-tabs";
|
|
29
|
+
import "@entro314labs/react-arc-tabs/styles.css";
|
|
41
30
|
|
|
42
31
|
const items: ArcTabItem[] = [
|
|
43
32
|
{ id: "overview", label: "Overview", content: <div>Overview content</div> },
|
|
@@ -50,24 +39,20 @@ export function Example() {
|
|
|
50
39
|
<ArcTabs
|
|
51
40
|
items={items}
|
|
52
41
|
defaultValue="overview"
|
|
53
|
-
size="md"
|
|
54
|
-
fit="content"
|
|
55
42
|
motionPreset="expressive"
|
|
56
43
|
motionDuration={320}
|
|
57
|
-
activationMode="automatic"
|
|
58
44
|
ariaLabel="Project sections"
|
|
59
|
-
accentColor="#5b4ff1"
|
|
60
45
|
/>
|
|
61
46
|
);
|
|
62
47
|
}
|
|
63
48
|
```
|
|
64
49
|
|
|
65
|
-
##
|
|
50
|
+
## Tailwind CSS v4+ usage
|
|
66
51
|
|
|
67
|
-
`ArcTabsTailwind`
|
|
52
|
+
`ArcTabsTailwind` ships with utility-class styling, so you do not import `styles.css`.
|
|
68
53
|
|
|
69
54
|
```tsx
|
|
70
|
-
import { ArcTabsTailwind, type ArcTabItem } from "react-arc-tabs";
|
|
55
|
+
import { ArcTabsTailwind, type ArcTabItem } from "@entro314labs/react-arc-tabs";
|
|
71
56
|
|
|
72
57
|
const items: ArcTabItem[] = [
|
|
73
58
|
{ id: "overview", label: "Overview", content: <div>Overview content</div> },
|
|
@@ -82,10 +67,6 @@ export function ExampleTailwind() {
|
|
|
82
67
|
defaultValue="overview"
|
|
83
68
|
fit="equal"
|
|
84
69
|
motionPreset="expressive"
|
|
85
|
-
motionDuration={320}
|
|
86
|
-
activationMode="automatic"
|
|
87
|
-
ariaLabel="Project sections"
|
|
88
|
-
accentColor="#4f46e5"
|
|
89
70
|
classNames={{
|
|
90
71
|
root: "max-w-3xl",
|
|
91
72
|
panel: "prose prose-sm max-w-none"
|
|
@@ -95,27 +76,22 @@ export function ExampleTailwind() {
|
|
|
95
76
|
}
|
|
96
77
|
```
|
|
97
78
|
|
|
98
|
-
### Tailwind
|
|
79
|
+
### Tailwind source scanning
|
|
99
80
|
|
|
100
|
-
If
|
|
81
|
+
If your setup does not automatically scan classes from installed dependencies, add:
|
|
101
82
|
|
|
102
83
|
```css
|
|
103
84
|
@import "tailwindcss";
|
|
104
|
-
|
|
105
|
-
/* npm/yarn/pnpm install use-case */
|
|
106
|
-
@source "../node_modules/react-arc-tabs/dist/**/*.{js,cjs}";
|
|
107
|
-
|
|
108
|
-
/* monorepo/local workspace use-case (adjust path) */
|
|
109
|
-
/* @source "../../packages/react-arc-tabs/src/**/*.{ts,tsx}"; */
|
|
85
|
+
@source "../node_modules/@entro314labs/react-arc-tabs/dist/**/*.{js,cjs}";
|
|
110
86
|
```
|
|
111
87
|
|
|
112
|
-
##
|
|
88
|
+
## Next.js usage
|
|
113
89
|
|
|
114
90
|
```tsx
|
|
115
91
|
"use client";
|
|
116
92
|
|
|
117
|
-
import { ArcTabs, type ArcTabItem } from "react-arc-tabs";
|
|
118
|
-
import "react-arc-tabs/styles.css";
|
|
93
|
+
import { ArcTabs, type ArcTabItem } from "@entro314labs/react-arc-tabs";
|
|
94
|
+
import "@entro314labs/react-arc-tabs/styles.css";
|
|
119
95
|
|
|
120
96
|
const tabs: ArcTabItem[] = [
|
|
121
97
|
{ id: "summary", label: "Summary", content: <div>Summary panel</div> },
|
|
@@ -128,40 +104,19 @@ export default function Page() {
|
|
|
128
104
|
}
|
|
129
105
|
```
|
|
130
106
|
|
|
131
|
-
|
|
107
|
+
## Motion presets
|
|
132
108
|
|
|
133
|
-
|
|
134
|
-
|
|
109
|
+
- `none`: no animation
|
|
110
|
+
- `subtle`: minimal transitions (default)
|
|
111
|
+
- `expressive`: sliding active backdrop + stronger panel motion
|
|
135
112
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const tabs: ArcTabItem[] = [
|
|
139
|
-
{ id: "summary", label: "Summary", content: <div>Summary panel</div> },
|
|
140
|
-
{ id: "billing", label: "Billing", content: <div>Billing panel</div> },
|
|
141
|
-
{ id: "usage", label: "Usage", content: <div>Usage panel</div> }
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
export default function Page() {
|
|
145
|
-
return <ArcTabsTailwind items={tabs} defaultValue="summary" ariaLabel="Account tabs" />;
|
|
146
|
-
}
|
|
113
|
+
```tsx
|
|
114
|
+
<ArcTabs motionPreset="expressive" motionDuration={320} />
|
|
147
115
|
```
|
|
148
116
|
|
|
149
|
-
##
|
|
150
|
-
|
|
151
|
-
You can theme via props or by overriding CSS variables on `.arc-tabs`:
|
|
152
|
-
|
|
153
|
-
- `--arc-radius`
|
|
154
|
-
- `--arc-gap`
|
|
155
|
-
- `--arc-accent`
|
|
156
|
-
- `--arc-tab-bg`
|
|
157
|
-
- `--arc-tab-hover-bg`
|
|
158
|
-
- `--arc-panel-bg`
|
|
159
|
-
- `--arc-panel-border`
|
|
160
|
-
- `--arc-panel-padding`
|
|
161
|
-
|
|
162
|
-
## API
|
|
117
|
+
## Main API
|
|
163
118
|
|
|
164
|
-
`ArcTabs`
|
|
119
|
+
### `ArcTabs` / `ArcTabsTailwind`
|
|
165
120
|
|
|
166
121
|
- `items: ArcTabItem[]`
|
|
167
122
|
- `value?: string`
|
|
@@ -173,39 +128,19 @@ You can theme via props or by overriding CSS variables on `.arc-tabs`:
|
|
|
173
128
|
- `motionPreset?: "none" | "subtle" | "expressive"`
|
|
174
129
|
- `motionDuration?: number`
|
|
175
130
|
- `keepMounted?: boolean`
|
|
176
|
-
- `
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- `
|
|
185
|
-
- `
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
- all `ArcTabs` props
|
|
190
|
-
- `classNames?: ArcTabsTailwindClassNames`
|
|
191
|
-
|
|
192
|
-
`ArcTabsTailwindClassNames` slots:
|
|
193
|
-
|
|
194
|
-
- `root`
|
|
195
|
-
- `list`
|
|
196
|
-
- `indicator`
|
|
197
|
-
- `item`
|
|
198
|
-
- `tab`
|
|
199
|
-
- `tabSelected`
|
|
200
|
-
- `tabUnselected`
|
|
201
|
-
- `tabDisabled`
|
|
202
|
-
- `icon`
|
|
203
|
-
- `text`
|
|
204
|
-
- `badge`
|
|
205
|
-
- `panels`
|
|
206
|
-
- `panel`
|
|
207
|
-
|
|
208
|
-
`ArcTabItem`:
|
|
131
|
+
- `ariaLabel?: string`
|
|
132
|
+
|
|
133
|
+
Additional styling/theming props are available for both variants.
|
|
134
|
+
|
|
135
|
+
### Tailwind slot overrides
|
|
136
|
+
|
|
137
|
+
`ArcTabsTailwind` supports `classNames` slots:
|
|
138
|
+
|
|
139
|
+
- `root`, `list`, `indicator`, `item`, `tab`
|
|
140
|
+
- `tabSelected`, `tabUnselected`, `tabDisabled`
|
|
141
|
+
- `icon`, `text`, `badge`, `panels`, `panel`
|
|
142
|
+
|
|
143
|
+
### `ArcTabItem`
|
|
209
144
|
|
|
210
145
|
- `id: string`
|
|
211
146
|
- `label: ReactNode`
|
|
@@ -213,3 +148,15 @@ You can theme via props or by overriding CSS variables on `.arc-tabs`:
|
|
|
213
148
|
- `disabled?: boolean`
|
|
214
149
|
- `icon?: ReactNode`
|
|
215
150
|
- `badge?: ReactNode`
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pnpm install
|
|
156
|
+
pnpm run typecheck
|
|
157
|
+
pnpm run build
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
package/dist/ArcTabs.css
CHANGED
|
@@ -129,12 +129,12 @@
|
|
|
129
129
|
font-size: 1rem;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
.arc-tabs__tab:not([aria-selected=
|
|
132
|
+
.arc-tabs__tab:not([aria-selected='true']):not(:disabled):hover {
|
|
133
133
|
background: var(--arc-tab-hover-bg);
|
|
134
134
|
transform: translateY(1px);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
.arc-tabs__tab:not([aria-selected=
|
|
137
|
+
.arc-tabs__tab:not([aria-selected='true']):not(:disabled):active {
|
|
138
138
|
transform: translateY(2px);
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
|
|
151
151
|
.arc-tabs__tab::before,
|
|
152
152
|
.arc-tabs__tab::after {
|
|
153
|
-
content:
|
|
153
|
+
content: '';
|
|
154
154
|
position: absolute;
|
|
155
155
|
bottom: 0;
|
|
156
156
|
width: calc(var(--arc-radius) + var(--arc-border-width));
|
|
@@ -165,8 +165,8 @@
|
|
|
165
165
|
transform var(--arc-motion-duration) var(--arc-motion-easing);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
.arc-tabs__tab[aria-selected=
|
|
169
|
-
.arc-tabs__tab[aria-selected=
|
|
168
|
+
.arc-tabs__tab[aria-selected='true']::before,
|
|
169
|
+
.arc-tabs__tab[aria-selected='true']::after {
|
|
170
170
|
opacity: 1;
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -175,8 +175,7 @@
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
.arc-tabs__tab:not(:first-child)::before {
|
|
178
|
-
transform: translateX(-100%)
|
|
179
|
-
translateY(calc(var(--arc-gap) + var(--arc-border-width)));
|
|
178
|
+
transform: translateX(-100%) translateY(calc(var(--arc-gap) + var(--arc-border-width)));
|
|
180
179
|
border-bottom-right-radius: var(--arc-radius);
|
|
181
180
|
box-shadow: var(--arc-border-width) var(--arc-radius) 0 var(--arc-panel-bg);
|
|
182
181
|
}
|
|
@@ -186,14 +185,12 @@
|
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
.arc-tabs__tab:not(:last-child)::after {
|
|
189
|
-
transform: translateX(100%)
|
|
190
|
-
translateY(calc(var(--arc-gap) + var(--arc-border-width)));
|
|
188
|
+
transform: translateX(100%) translateY(calc(var(--arc-gap) + var(--arc-border-width)));
|
|
191
189
|
border-bottom-left-radius: var(--arc-radius);
|
|
192
|
-
box-shadow: calc(var(--arc-border-width) * -1) var(--arc-radius) 0
|
|
193
|
-
var(--arc-panel-bg);
|
|
190
|
+
box-shadow: calc(var(--arc-border-width) * -1) var(--arc-radius) 0 var(--arc-panel-bg);
|
|
194
191
|
}
|
|
195
192
|
|
|
196
|
-
.arc-tabs__tab[aria-selected=
|
|
193
|
+
.arc-tabs__tab[aria-selected='true'] {
|
|
197
194
|
z-index: 3;
|
|
198
195
|
color: color-mix(in srgb, var(--arc-accent) 85%, black 15%);
|
|
199
196
|
border-color: var(--arc-panel-bg);
|
|
@@ -203,7 +200,7 @@
|
|
|
203
200
|
box-shadow: 0 calc(var(--arc-gap) + var(--arc-border-width)) 0 var(--arc-panel-bg);
|
|
204
201
|
}
|
|
205
202
|
|
|
206
|
-
.arc-tabs--motion-expressive .arc-tabs__tab[aria-selected=
|
|
203
|
+
.arc-tabs--motion-expressive .arc-tabs__tab[aria-selected='true'] {
|
|
207
204
|
background: transparent;
|
|
208
205
|
}
|
|
209
206
|
|
package/dist/index.cjs
CHANGED
|
@@ -49,7 +49,7 @@ var getNextEnabledIndex = (enabledIndices, currentIndex, direction) => {
|
|
|
49
49
|
if (!enabledIndices.length) return -1;
|
|
50
50
|
const currentPosition = enabledIndices.indexOf(currentIndex);
|
|
51
51
|
if (currentPosition === -1) {
|
|
52
|
-
return direction === 1 ? enabledIndices[0] ?? -1 : enabledIndices
|
|
52
|
+
return direction === 1 ? enabledIndices[0] ?? -1 : enabledIndices.at(-1) ?? -1;
|
|
53
53
|
}
|
|
54
54
|
const nextPosition = (currentPosition + direction + enabledIndices.length) % enabledIndices.length;
|
|
55
55
|
return enabledIndices[nextPosition] ?? -1;
|
|
@@ -90,15 +90,10 @@ function ArcTabs({
|
|
|
90
90
|
[listId, reactId]
|
|
91
91
|
);
|
|
92
92
|
const isControlled = value !== void 0;
|
|
93
|
-
const firstEnabledIndex = React.useMemo(
|
|
94
|
-
() => findFirstEnabledIndex(items),
|
|
95
|
-
[items]
|
|
96
|
-
);
|
|
93
|
+
const firstEnabledIndex = React.useMemo(() => findFirstEnabledIndex(items), [items]);
|
|
97
94
|
const [uncontrolledValue, setUncontrolledValue] = React.useState(() => {
|
|
98
95
|
const requested = defaultValue;
|
|
99
|
-
const requestedMatch = items.find(
|
|
100
|
-
(item) => !item.disabled && item.id === requested
|
|
101
|
-
);
|
|
96
|
+
const requestedMatch = items.find((item) => !item.disabled && item.id === requested);
|
|
102
97
|
if (requestedMatch) return requestedMatch.id;
|
|
103
98
|
return firstEnabledIndex >= 0 ? items[firstEnabledIndex]?.id : void 0;
|
|
104
99
|
});
|
|
@@ -136,7 +131,9 @@ function ArcTabs({
|
|
|
136
131
|
const hasMountedRef = React.useRef(false);
|
|
137
132
|
const previousSelectedIndexRef = React.useRef(selectedIndex);
|
|
138
133
|
const [hasInteracted, setHasInteracted] = React.useState(false);
|
|
139
|
-
const [panelDirection, setPanelDirection] = React.useState(
|
|
134
|
+
const [panelDirection, setPanelDirection] = React.useState(
|
|
135
|
+
"none"
|
|
136
|
+
);
|
|
140
137
|
const [indicator, setIndicator] = React.useState({
|
|
141
138
|
x: 0,
|
|
142
139
|
width: 0,
|
|
@@ -221,12 +218,18 @@ function ArcTabs({
|
|
|
221
218
|
if (!showSlidingIndicator) return;
|
|
222
219
|
const listElement = listRef.current;
|
|
223
220
|
if (!listElement) return;
|
|
224
|
-
const onResize = () =>
|
|
225
|
-
|
|
221
|
+
const onResize = () => {
|
|
222
|
+
syncIndicator();
|
|
223
|
+
};
|
|
224
|
+
const onScroll = () => {
|
|
225
|
+
syncIndicator();
|
|
226
|
+
};
|
|
226
227
|
const frame = requestAnimationFrame(syncIndicator);
|
|
227
228
|
let observer = null;
|
|
228
229
|
if (typeof ResizeObserver !== "undefined") {
|
|
229
|
-
observer = new ResizeObserver(() =>
|
|
230
|
+
observer = new ResizeObserver(() => {
|
|
231
|
+
syncIndicator();
|
|
232
|
+
});
|
|
230
233
|
observer.observe(listElement);
|
|
231
234
|
tabRefs.current.forEach((tabElement) => {
|
|
232
235
|
if (tabElement) observer?.observe(tabElement);
|
|
@@ -249,7 +252,7 @@ function ArcTabs({
|
|
|
249
252
|
if (!panelElement || typeof panelElement.animate !== "function") {
|
|
250
253
|
return;
|
|
251
254
|
}
|
|
252
|
-
if (typeof window !== "undefined" &&
|
|
255
|
+
if (typeof globalThis.window !== "undefined" && globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
|
253
256
|
return;
|
|
254
257
|
}
|
|
255
258
|
const offsetX = motionPreset === "expressive" ? panelDirection === "forward" ? 20 : panelDirection === "backward" ? -20 : 0 : 0;
|
|
@@ -277,13 +280,7 @@ function ArcTabs({
|
|
|
277
280
|
return () => {
|
|
278
281
|
animation.cancel();
|
|
279
282
|
};
|
|
280
|
-
}, [
|
|
281
|
-
selectedIndex,
|
|
282
|
-
hasInteracted,
|
|
283
|
-
motionPreset,
|
|
284
|
-
panelDirection,
|
|
285
|
-
effectiveMotionDuration
|
|
286
|
-
]);
|
|
283
|
+
}, [selectedIndex, hasInteracted, motionPreset, panelDirection, effectiveMotionDuration]);
|
|
287
284
|
const handleTabKeyDown = React.useCallback(
|
|
288
285
|
(event, index) => {
|
|
289
286
|
if (!enabledIndices.length) return;
|
|
@@ -317,7 +314,7 @@ function ArcTabs({
|
|
|
317
314
|
}
|
|
318
315
|
case "End": {
|
|
319
316
|
event.preventDefault();
|
|
320
|
-
const last = enabledIndices
|
|
317
|
+
const last = enabledIndices.at(-1);
|
|
321
318
|
if (last !== void 0) {
|
|
322
319
|
focusTabIndex(last);
|
|
323
320
|
if (activationMode === "automatic") selectTab(last);
|
|
@@ -417,10 +414,7 @@ function ArcTabs({
|
|
|
417
414
|
{
|
|
418
415
|
"aria-hidden": "true",
|
|
419
416
|
role: "presentation",
|
|
420
|
-
className: joinClassNames(
|
|
421
|
-
"arc-tabs__active-indicator",
|
|
422
|
-
indicator.ready && "is-ready"
|
|
423
|
-
),
|
|
417
|
+
className: joinClassNames("arc-tabs__active-indicator", indicator.ready && "is-ready"),
|
|
424
418
|
style: indicatorStyle
|
|
425
419
|
}
|
|
426
420
|
) : null,
|
|
@@ -445,9 +439,15 @@ function ArcTabs({
|
|
|
445
439
|
tabIndex: tabIndexValue,
|
|
446
440
|
disabled,
|
|
447
441
|
className: "arc-tabs__tab",
|
|
448
|
-
onFocus: () =>
|
|
449
|
-
|
|
450
|
-
|
|
442
|
+
onFocus: () => {
|
|
443
|
+
setFocusedIndex(index);
|
|
444
|
+
},
|
|
445
|
+
onClick: () => {
|
|
446
|
+
selectTab(index);
|
|
447
|
+
},
|
|
448
|
+
onKeyDown: (event) => {
|
|
449
|
+
handleTabKeyDown(event, index);
|
|
450
|
+
},
|
|
451
451
|
children: renderTabLabel ? renderTabLabel(item, state) : renderDefaultLabel(item)
|
|
452
452
|
}
|
|
453
453
|
) }, item.id);
|
|
@@ -519,7 +519,7 @@ var getNextEnabledIndex2 = (enabledIndices, currentIndex, direction) => {
|
|
|
519
519
|
if (!enabledIndices.length) return -1;
|
|
520
520
|
const currentPosition = enabledIndices.indexOf(currentIndex);
|
|
521
521
|
if (currentPosition === -1) {
|
|
522
|
-
return direction === 1 ? enabledIndices[0] ?? -1 : enabledIndices
|
|
522
|
+
return direction === 1 ? enabledIndices[0] ?? -1 : enabledIndices.at(-1) ?? -1;
|
|
523
523
|
}
|
|
524
524
|
const nextPosition = (currentPosition + direction + enabledIndices.length) % enabledIndices.length;
|
|
525
525
|
return enabledIndices[nextPosition] ?? -1;
|
|
@@ -566,15 +566,10 @@ function ArcTabsTailwind({
|
|
|
566
566
|
[listId, reactId]
|
|
567
567
|
);
|
|
568
568
|
const isControlled = value !== void 0;
|
|
569
|
-
const firstEnabledIndex = React2.useMemo(
|
|
570
|
-
() => findFirstEnabledIndex2(items),
|
|
571
|
-
[items]
|
|
572
|
-
);
|
|
569
|
+
const firstEnabledIndex = React2.useMemo(() => findFirstEnabledIndex2(items), [items]);
|
|
573
570
|
const [uncontrolledValue, setUncontrolledValue] = React2.useState(() => {
|
|
574
571
|
const requested = defaultValue;
|
|
575
|
-
const requestedMatch = items.find(
|
|
576
|
-
(item) => !item.disabled && item.id === requested
|
|
577
|
-
);
|
|
572
|
+
const requestedMatch = items.find((item) => !item.disabled && item.id === requested);
|
|
578
573
|
if (requestedMatch) return requestedMatch.id;
|
|
579
574
|
return firstEnabledIndex >= 0 ? items[firstEnabledIndex]?.id : void 0;
|
|
580
575
|
});
|
|
@@ -612,7 +607,9 @@ function ArcTabsTailwind({
|
|
|
612
607
|
const hasMountedRef = React2.useRef(false);
|
|
613
608
|
const previousSelectedIndexRef = React2.useRef(selectedIndex);
|
|
614
609
|
const [hasInteracted, setHasInteracted] = React2.useState(false);
|
|
615
|
-
const [panelDirection, setPanelDirection] = React2.useState(
|
|
610
|
+
const [panelDirection, setPanelDirection] = React2.useState(
|
|
611
|
+
"none"
|
|
612
|
+
);
|
|
616
613
|
const [indicator, setIndicator] = React2.useState({
|
|
617
614
|
x: 0,
|
|
618
615
|
width: 0,
|
|
@@ -697,12 +694,18 @@ function ArcTabsTailwind({
|
|
|
697
694
|
if (!showSlidingIndicator) return;
|
|
698
695
|
const listElement = listRef.current;
|
|
699
696
|
if (!listElement) return;
|
|
700
|
-
const onResize = () =>
|
|
701
|
-
|
|
697
|
+
const onResize = () => {
|
|
698
|
+
syncIndicator();
|
|
699
|
+
};
|
|
700
|
+
const onScroll = () => {
|
|
701
|
+
syncIndicator();
|
|
702
|
+
};
|
|
702
703
|
const frame = requestAnimationFrame(syncIndicator);
|
|
703
704
|
let observer = null;
|
|
704
705
|
if (typeof ResizeObserver !== "undefined") {
|
|
705
|
-
observer = new ResizeObserver(() =>
|
|
706
|
+
observer = new ResizeObserver(() => {
|
|
707
|
+
syncIndicator();
|
|
708
|
+
});
|
|
706
709
|
observer.observe(listElement);
|
|
707
710
|
tabRefs.current.forEach((tabElement) => {
|
|
708
711
|
if (tabElement) observer?.observe(tabElement);
|
|
@@ -725,7 +728,7 @@ function ArcTabsTailwind({
|
|
|
725
728
|
if (!panelElement || typeof panelElement.animate !== "function") {
|
|
726
729
|
return;
|
|
727
730
|
}
|
|
728
|
-
if (typeof window !== "undefined" &&
|
|
731
|
+
if (typeof globalThis.window !== "undefined" && globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
|
729
732
|
return;
|
|
730
733
|
}
|
|
731
734
|
const offsetX = motionPreset === "expressive" ? panelDirection === "forward" ? 20 : panelDirection === "backward" ? -20 : 0 : 0;
|
|
@@ -753,13 +756,7 @@ function ArcTabsTailwind({
|
|
|
753
756
|
return () => {
|
|
754
757
|
animation.cancel();
|
|
755
758
|
};
|
|
756
|
-
}, [
|
|
757
|
-
selectedIndex,
|
|
758
|
-
hasInteracted,
|
|
759
|
-
motionPreset,
|
|
760
|
-
panelDirection,
|
|
761
|
-
effectiveMotionDuration
|
|
762
|
-
]);
|
|
759
|
+
}, [selectedIndex, hasInteracted, motionPreset, panelDirection, effectiveMotionDuration]);
|
|
763
760
|
const handleTabKeyDown = React2.useCallback(
|
|
764
761
|
(event, index) => {
|
|
765
762
|
if (!enabledIndices.length) return;
|
|
@@ -793,7 +790,7 @@ function ArcTabsTailwind({
|
|
|
793
790
|
}
|
|
794
791
|
case "End": {
|
|
795
792
|
event.preventDefault();
|
|
796
|
-
const last = enabledIndices
|
|
793
|
+
const last = enabledIndices.at(-1);
|
|
797
794
|
if (last !== void 0) {
|
|
798
795
|
focusTabIndex(last);
|
|
799
796
|
if (activationMode === "automatic") selectTab(last);
|
|
@@ -958,9 +955,15 @@ function ArcTabsTailwind({
|
|
|
958
955
|
tabIndex: tabIndexValue,
|
|
959
956
|
disabled,
|
|
960
957
|
className: tabClassName,
|
|
961
|
-
onFocus: () =>
|
|
962
|
-
|
|
963
|
-
|
|
958
|
+
onFocus: () => {
|
|
959
|
+
setFocusedIndex(index);
|
|
960
|
+
},
|
|
961
|
+
onClick: () => {
|
|
962
|
+
selectTab(index);
|
|
963
|
+
},
|
|
964
|
+
onKeyDown: (event) => {
|
|
965
|
+
handleTabKeyDown(event, index);
|
|
966
|
+
},
|
|
964
967
|
"data-slot": "tab",
|
|
965
968
|
children: renderTabLabel ? renderTabLabel(item, state) : renderDefaultLabel(item)
|
|
966
969
|
}
|