@mshafiqyajid/react-tabs 0.1.0 → 0.3.0
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/dist/{chunk-UCRNSS7N.js → chunk-NNDW3W6L.js} +43 -34
- package/dist/chunk-NNDW3W6L.js.map +1 -0
- package/dist/index.cjs +41 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +1 -1
- package/dist/styled.cjs +260 -74
- package/dist/styled.cjs.map +1 -1
- package/dist/styled.d.cts +37 -2
- package/dist/styled.d.ts +37 -2
- package/dist/styled.js +221 -44
- package/dist/styled.js.map +1 -1
- package/dist/styles.css +120 -0
- package/package.json +1 -1
- package/dist/chunk-UCRNSS7N.js.map +0 -1
package/dist/styled.d.ts
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { TabsChangeReason, TabsActivation, TabsOrientation } from './index.js';
|
|
3
4
|
|
|
4
5
|
type TabsVariant = "line" | "solid" | "pill";
|
|
5
6
|
type TabsSize = "sm" | "md" | "lg";
|
|
6
|
-
type TabsTone = "neutral" | "primary";
|
|
7
|
+
type TabsTone = "neutral" | "primary" | "success" | "danger";
|
|
7
8
|
interface TabItem {
|
|
8
9
|
value: string;
|
|
9
10
|
label: ReactNode;
|
|
10
11
|
content: ReactNode;
|
|
11
12
|
disabled?: boolean;
|
|
13
|
+
/** When true, renders a × button on the tab. Pair with `onTabClose`. */
|
|
14
|
+
closable?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface TabsRenderTabContext {
|
|
17
|
+
tab: TabItem;
|
|
18
|
+
index: number;
|
|
19
|
+
isActive: boolean;
|
|
20
|
+
isDisabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface TabsRenderPanelContext {
|
|
23
|
+
tab: TabItem;
|
|
24
|
+
isActive: boolean;
|
|
12
25
|
}
|
|
13
26
|
interface TabsStyledProps {
|
|
14
27
|
tabs: TabItem[];
|
|
@@ -17,7 +30,29 @@ interface TabsStyledProps {
|
|
|
17
30
|
tone?: TabsTone;
|
|
18
31
|
defaultValue?: string;
|
|
19
32
|
value?: string;
|
|
20
|
-
|
|
33
|
+
/** Optional second arg reports the trigger reason ("click" | "keyboard" | "programmatic"). */
|
|
34
|
+
onChange?: (value: string, reason: TabsChangeReason) => void;
|
|
35
|
+
/** "automatic" (default) — arrow keys move focus AND activate. "manual" — arrows move focus only. */
|
|
36
|
+
activation?: TabsActivation;
|
|
37
|
+
/** Affects keyboard nav direction. Default "horizontal". */
|
|
38
|
+
orientation?: TabsOrientation;
|
|
39
|
+
/** Only mount panels after they've been activated at least once. Default: false (all panels mount eagerly). */
|
|
40
|
+
lazyMount?: boolean;
|
|
41
|
+
/** Keep all panels mounted regardless of activation (useful with `lazyMount` overrides). Default: false. */
|
|
42
|
+
forceMount?: boolean;
|
|
43
|
+
/** Fires when the × on a closable tab is clicked. */
|
|
44
|
+
onTabClose?: (value: string) => void;
|
|
45
|
+
/** When true, the tab list scrolls horizontally with chevron buttons at the edges instead of wrapping. */
|
|
46
|
+
scrollable?: boolean;
|
|
47
|
+
/** When true, tabs can be reordered by dragging. Fires `onReorder` with the new value order. */
|
|
48
|
+
sortable?: boolean;
|
|
49
|
+
onReorder?: (values: string[]) => void;
|
|
50
|
+
/** When true, the active tab is scrolled into view on activation. Default: true when `scrollable`. */
|
|
51
|
+
scrollActiveIntoView?: boolean;
|
|
52
|
+
/** Replace the default tab button content. The button shell (a11y, ref, key) stays owned by the component. */
|
|
53
|
+
renderTab?: (ctx: TabsRenderTabContext) => ReactNode;
|
|
54
|
+
/** Replace the default panel rendering. */
|
|
55
|
+
renderPanel?: (ctx: TabsRenderPanelContext) => ReactNode;
|
|
21
56
|
className?: string;
|
|
22
57
|
}
|
|
23
58
|
declare const TabsStyled: react.ForwardRefExoticComponent<TabsStyledProps & react.RefAttributes<HTMLDivElement>>;
|
package/dist/styled.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useTabs } from './chunk-
|
|
2
|
-
import { forwardRef, useRef,
|
|
1
|
+
import { useTabs } from './chunk-NNDW3W6L.js';
|
|
2
|
+
import { forwardRef, useState, useRef, useEffect, useLayoutEffect } from 'react';
|
|
3
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
6
|
-
function useIndicator(tabListRef, activeValue) {
|
|
6
|
+
function useIndicator(tabListRef, activeValue, orientation) {
|
|
7
7
|
const [style, setStyle] = useState({});
|
|
8
8
|
useIsoLayoutEffect(() => {
|
|
9
9
|
if (!tabListRef.current || activeValue === void 0) return;
|
|
@@ -16,20 +16,30 @@ function useIndicator(tabListRef, activeValue) {
|
|
|
16
16
|
if (!activeTab) return;
|
|
17
17
|
const listRect = list.getBoundingClientRect();
|
|
18
18
|
const tabRect = activeTab.getBoundingClientRect();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
if (orientation === "vertical") {
|
|
20
|
+
const y = tabRect.top - listRect.top;
|
|
21
|
+
const h = tabRect.height;
|
|
22
|
+
setStyle({
|
|
23
|
+
["--rtab-indicator-y"]: `${y}px`,
|
|
24
|
+
["--rtab-indicator-height"]: `${h}px`,
|
|
25
|
+
["--rtab-indicator-ready"]: "1"
|
|
26
|
+
});
|
|
27
|
+
} else {
|
|
28
|
+
const x = tabRect.left - listRect.left;
|
|
29
|
+
const w = tabRect.width;
|
|
30
|
+
setStyle({
|
|
31
|
+
["--rtab-indicator-x"]: `${x}px`,
|
|
32
|
+
["--rtab-indicator-width"]: `${w}px`,
|
|
33
|
+
["--rtab-indicator-ready"]: "1"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
26
36
|
};
|
|
27
37
|
measure();
|
|
28
38
|
if (typeof ResizeObserver === "undefined") return;
|
|
29
39
|
const ro = new ResizeObserver(measure);
|
|
30
40
|
ro.observe(tabListRef.current);
|
|
31
41
|
return () => ro.disconnect();
|
|
32
|
-
}, [activeValue, tabListRef]);
|
|
42
|
+
}, [activeValue, tabListRef, orientation]);
|
|
33
43
|
return style;
|
|
34
44
|
}
|
|
35
45
|
var TabsStyled = forwardRef(
|
|
@@ -41,54 +51,221 @@ var TabsStyled = forwardRef(
|
|
|
41
51
|
defaultValue,
|
|
42
52
|
value,
|
|
43
53
|
onChange,
|
|
54
|
+
activation = "automatic",
|
|
55
|
+
orientation = "horizontal",
|
|
56
|
+
lazyMount = false,
|
|
57
|
+
forceMount = false,
|
|
58
|
+
onTabClose,
|
|
59
|
+
scrollable = false,
|
|
60
|
+
sortable = false,
|
|
61
|
+
onReorder,
|
|
62
|
+
scrollActiveIntoView,
|
|
63
|
+
renderTab,
|
|
64
|
+
renderPanel,
|
|
44
65
|
className
|
|
45
66
|
}, ref) {
|
|
67
|
+
const autoScrollActive = scrollActiveIntoView ?? scrollable;
|
|
68
|
+
const [scrollState, setScrollState] = useState({
|
|
69
|
+
left: false,
|
|
70
|
+
right: false
|
|
71
|
+
});
|
|
72
|
+
const typeaheadBufferRef = useRef("");
|
|
73
|
+
const typeaheadTimerRef = useRef(null);
|
|
74
|
+
const dragValueRef = useRef(null);
|
|
46
75
|
const tabDefs = tabs.map((t) => ({ value: t.value, disabled: t.disabled }));
|
|
47
76
|
const resolvedDefault = defaultValue ?? tabs.find((t) => !t.disabled)?.value;
|
|
48
77
|
const { activeValue, getTabProps, getPanelProps } = useTabs({
|
|
49
78
|
tabs: tabDefs,
|
|
50
79
|
defaultValue: value === void 0 ? resolvedDefault : void 0,
|
|
51
80
|
value,
|
|
52
|
-
onChange
|
|
81
|
+
onChange,
|
|
82
|
+
activation,
|
|
83
|
+
orientation
|
|
53
84
|
});
|
|
54
85
|
const tabListRef = useRef(null);
|
|
55
|
-
const indicatorStyle = useIndicator(tabListRef, activeValue);
|
|
86
|
+
const indicatorStyle = useIndicator(tabListRef, activeValue, orientation);
|
|
87
|
+
const activatedRef = useRef(/* @__PURE__ */ new Set());
|
|
88
|
+
if (activeValue !== void 0) activatedRef.current.add(activeValue);
|
|
56
89
|
const rootClass = ["rtab-root", className].filter(Boolean).join(" ");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!scrollable) return;
|
|
92
|
+
const list = tabListRef.current;
|
|
93
|
+
if (!list) return;
|
|
94
|
+
const update = () => {
|
|
95
|
+
setScrollState({
|
|
96
|
+
left: list.scrollLeft > 4,
|
|
97
|
+
right: list.scrollLeft + list.clientWidth < list.scrollWidth - 4
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
update();
|
|
101
|
+
list.addEventListener("scroll", update, { passive: true });
|
|
102
|
+
const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(update) : null;
|
|
103
|
+
ro?.observe(list);
|
|
104
|
+
return () => {
|
|
105
|
+
list.removeEventListener("scroll", update);
|
|
106
|
+
ro?.disconnect();
|
|
107
|
+
};
|
|
108
|
+
}, [scrollable, tabs.length]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!autoScrollActive || !activeValue) return;
|
|
111
|
+
const list = tabListRef.current;
|
|
112
|
+
if (!list) return;
|
|
113
|
+
const activeEl = list.querySelector(`[aria-selected="true"]`);
|
|
114
|
+
if (!activeEl) return;
|
|
115
|
+
activeEl.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
|
|
116
|
+
}, [autoScrollActive, activeValue]);
|
|
117
|
+
const scrollByAmount = (delta) => {
|
|
118
|
+
tabListRef.current?.scrollBy({ left: delta, behavior: "smooth" });
|
|
119
|
+
};
|
|
120
|
+
const handleTypeahead = (e) => {
|
|
121
|
+
if (e.key.length !== 1 || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
122
|
+
typeaheadBufferRef.current = (typeaheadBufferRef.current + e.key).toLowerCase();
|
|
123
|
+
if (typeaheadTimerRef.current) clearTimeout(typeaheadTimerRef.current);
|
|
124
|
+
typeaheadTimerRef.current = setTimeout(() => {
|
|
125
|
+
typeaheadBufferRef.current = "";
|
|
126
|
+
}, 600);
|
|
127
|
+
const buffer = typeaheadBufferRef.current;
|
|
128
|
+
const startIdx = Math.max(0, tabs.findIndex((t) => t.value === activeValue));
|
|
129
|
+
for (let i = 1; i <= tabs.length; i++) {
|
|
130
|
+
const idx = (startIdx + i) % tabs.length;
|
|
131
|
+
const tab = tabs[idx];
|
|
132
|
+
if (!tab || tab.disabled) continue;
|
|
133
|
+
const labelStr = typeof tab.label === "string" ? tab.label : "";
|
|
134
|
+
if (labelStr.toLowerCase().startsWith(buffer)) {
|
|
135
|
+
const list = tabListRef.current;
|
|
136
|
+
const btn = list?.querySelector(`[data-value="${tab.value}"]`);
|
|
137
|
+
btn?.click();
|
|
138
|
+
btn?.focus();
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const handleDragStart = (val) => (e) => {
|
|
144
|
+
if (!sortable) return;
|
|
145
|
+
dragValueRef.current = val;
|
|
146
|
+
e.dataTransfer.effectAllowed = "move";
|
|
147
|
+
};
|
|
148
|
+
const handleDragOver = (val) => (e) => {
|
|
149
|
+
if (!sortable || !dragValueRef.current || dragValueRef.current === val) return;
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
e.dataTransfer.dropEffect = "move";
|
|
152
|
+
};
|
|
153
|
+
const handleDrop = (val) => (e) => {
|
|
154
|
+
if (!sortable) return;
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
const from = dragValueRef.current;
|
|
157
|
+
dragValueRef.current = null;
|
|
158
|
+
if (!from || from === val || !onReorder) return;
|
|
159
|
+
const order = tabs.map((t) => t.value);
|
|
160
|
+
const fromIdx = order.indexOf(from);
|
|
161
|
+
const toIdx = order.indexOf(val);
|
|
162
|
+
if (fromIdx === -1 || toIdx === -1) return;
|
|
163
|
+
const next = [...order];
|
|
164
|
+
next.splice(fromIdx, 1);
|
|
165
|
+
next.splice(toIdx, 0, from);
|
|
166
|
+
onReorder(next);
|
|
167
|
+
};
|
|
168
|
+
return /* @__PURE__ */ jsxs(
|
|
169
|
+
"div",
|
|
170
|
+
{
|
|
171
|
+
ref,
|
|
172
|
+
className: rootClass,
|
|
173
|
+
"data-orientation": orientation,
|
|
174
|
+
"data-scrollable": scrollable || void 0,
|
|
175
|
+
onKeyDown: handleTypeahead,
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ jsxs("div", { className: "rtab-list-wrap", children: [
|
|
178
|
+
scrollable && scrollState.left && /* @__PURE__ */ jsx(
|
|
179
|
+
"button",
|
|
180
|
+
{
|
|
181
|
+
type: "button",
|
|
182
|
+
className: "rtab-scroll-btn rtab-scroll-btn--left",
|
|
183
|
+
"aria-label": "Scroll left",
|
|
184
|
+
onClick: () => scrollByAmount(-200),
|
|
185
|
+
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "12", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M7.5 3l-3 3 3 3" }) })
|
|
186
|
+
}
|
|
187
|
+
),
|
|
188
|
+
/* @__PURE__ */ jsxs(
|
|
189
|
+
"div",
|
|
190
|
+
{
|
|
191
|
+
ref: tabListRef,
|
|
192
|
+
role: "tablist",
|
|
193
|
+
"aria-orientation": orientation,
|
|
194
|
+
className: "rtab-list",
|
|
195
|
+
"data-variant": variant,
|
|
196
|
+
"data-size": size,
|
|
197
|
+
"data-tone": tone,
|
|
198
|
+
"data-orientation": orientation,
|
|
199
|
+
"data-scrollable": scrollable || void 0,
|
|
200
|
+
style: indicatorStyle,
|
|
201
|
+
children: [
|
|
202
|
+
tabs.map((tab, index) => {
|
|
203
|
+
const isActive = activeValue === tab.value;
|
|
204
|
+
const isDisabled = !!tab.disabled;
|
|
205
|
+
const tabProps = getTabProps(tab.value, { disabled: tab.disabled });
|
|
206
|
+
return /* @__PURE__ */ jsxs(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
"data-value": tab.value,
|
|
210
|
+
"data-closable": tab.closable || void 0,
|
|
211
|
+
className: "rtab-tab",
|
|
212
|
+
...tabProps,
|
|
213
|
+
draggable: sortable && !isDisabled ? true : void 0,
|
|
214
|
+
onDragStart: sortable ? handleDragStart(tab.value) : void 0,
|
|
215
|
+
onDragOver: sortable ? handleDragOver(tab.value) : void 0,
|
|
216
|
+
onDrop: sortable ? handleDrop(tab.value) : void 0,
|
|
217
|
+
children: [
|
|
218
|
+
renderTab ? renderTab({ tab, index, isActive, isDisabled }) : tab.label,
|
|
219
|
+
tab.closable && /* @__PURE__ */ jsx(
|
|
220
|
+
"span",
|
|
221
|
+
{
|
|
222
|
+
className: "rtab-close",
|
|
223
|
+
role: "button",
|
|
224
|
+
tabIndex: -1,
|
|
225
|
+
"aria-label": `Close ${typeof tab.label === "string" ? tab.label : "tab"}`,
|
|
226
|
+
onClick: (e) => {
|
|
227
|
+
e.stopPropagation();
|
|
228
|
+
onTabClose?.(tab.value);
|
|
229
|
+
},
|
|
230
|
+
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "10", height: "10", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 3l6 6M9 3l-6 6" }) })
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
tab.value
|
|
236
|
+
);
|
|
237
|
+
}),
|
|
238
|
+
(variant === "line" || variant === "solid" || variant === "pill") && /* @__PURE__ */ jsx("span", { className: "rtab-indicator", "aria-hidden": "true" })
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
scrollable && scrollState.right && /* @__PURE__ */ jsx(
|
|
70
243
|
"button",
|
|
71
244
|
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
245
|
+
type: "button",
|
|
246
|
+
className: "rtab-scroll-btn rtab-scroll-btn--right",
|
|
247
|
+
"aria-label": "Scroll right",
|
|
248
|
+
onClick: () => scrollByAmount(200),
|
|
249
|
+
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "12", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4.5 3l3 3-3 3" }) })
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
] }),
|
|
253
|
+
/* @__PURE__ */ jsx("div", { className: "rtab-panels", children: tabs.map((tab) => {
|
|
254
|
+
const isActive = activeValue === tab.value;
|
|
255
|
+
const shouldRender = forceMount || !lazyMount || activatedRef.current.has(tab.value);
|
|
256
|
+
return /* @__PURE__ */ jsx(
|
|
257
|
+
"div",
|
|
258
|
+
{
|
|
259
|
+
className: "rtab-panel",
|
|
260
|
+
...getPanelProps(tab.value),
|
|
261
|
+
children: shouldRender ? renderPanel ? renderPanel({ tab, isActive }) : tab.content : null
|
|
75
262
|
},
|
|
76
263
|
tab.value
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/* @__PURE__ */ jsx("div", { className: "rtab-panels", children: tabs.map((tab) => /* @__PURE__ */ jsx(
|
|
83
|
-
"div",
|
|
84
|
-
{
|
|
85
|
-
className: "rtab-panel",
|
|
86
|
-
...getPanelProps(tab.value),
|
|
87
|
-
children: tab.content
|
|
88
|
-
},
|
|
89
|
-
tab.value
|
|
90
|
-
)) })
|
|
91
|
-
] });
|
|
264
|
+
);
|
|
265
|
+
}) })
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
);
|
|
92
269
|
}
|
|
93
270
|
);
|
|
94
271
|
|
package/dist/styled.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/styled/TabsStyled.tsx"],"names":["TabsStyled"],"mappings":";;;;AAkCA,IAAM,kBAAA,GACJ,OAAO,MAAA,KAAW,WAAA,GAAc,eAAA,GAAkB,SAAA;AAEpD,SAAS,YAAA,CACP,YACA,WAAA,EACA;AACA,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAAwB,EAAE,CAAA;AAEpD,EAAA,kBAAA,CAAmB,MAAM;AACvB,IAAA,IAAI,CAAC,UAAA,CAAW,OAAA,IAAW,WAAA,KAAgB,MAAA,EAAW;AAEtD,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,MAAM,OAAO,UAAA,CAAW,OAAA;AACxB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,MAAM,YAAY,IAAA,CAAK,aAAA;AAAA,QACrB,CAAA,sBAAA;AAAA,OACF;AACA,MAAA,IAAI,CAAC,SAAA,EAAW;AAEhB,MAAA,MAAM,QAAA,GAAW,KAAK,qBAAA,EAAsB;AAC5C,MAAA,MAAM,OAAA,GAAU,UAAU,qBAAA,EAAsB;AAChD,MAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,IAAA,GAAO,QAAA,CAAS,IAAA;AAClC,MAAA,MAAM,IAAI,OAAA,CAAQ,KAAA;AAElB,MAAA,QAAA,CAAS;AAAA,QACP,CAAC,oBAA8B,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,QACtC,CAAC,wBAAkC,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,QAC1C,CAAC,wBAAkC,GAAG;AAAA,OACvC,CAAA;AAAA,IACH,CAAA;AAEA,IAAA,OAAA,EAAQ;AAER,IAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAC3C,IAAA,MAAM,EAAA,GAAK,IAAI,cAAA,CAAe,OAAO,CAAA;AACrC,IAAA,EAAA,CAAG,OAAA,CAAQ,WAAW,OAAO,CAAA;AAC7B,IAAA,OAAO,MAAM,GAAG,UAAA,EAAW;AAAA,EAC7B,CAAA,EAAG,CAAC,WAAA,EAAa,UAAU,CAAC,CAAA;AAE5B,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,UAAA,GAAa,UAAA;AAAA,EACxB,SAASA,WAAAA,CACP;AAAA,IACE,IAAA;AAAA,IACA,OAAA,GAAU,MAAA;AAAA,IACV,IAAA,GAAO,IAAA;AAAA,IACP,IAAA,GAAO,SAAA;AAAA,IACP,YAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAO,QAAA,EAAU,CAAA,CAAE,QAAA,EAAS,CAAE,CAAA;AAE1E,IAAA,MAAM,eAAA,GACJ,gBAAgB,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,QAAQ,CAAA,EAAG,KAAA;AAEjD,IAAA,MAAM,EAAE,WAAA,EAAa,WAAA,EAAa,aAAA,KAAkB,OAAA,CAAQ;AAAA,MAC1D,IAAA,EAAM,OAAA;AAAA,MACN,YAAA,EAAc,KAAA,KAAU,MAAA,GAAY,eAAA,GAAkB,MAAA;AAAA,MACtD,KAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAA,GAAa,OAAuB,IAAI,CAAA;AAC9C,IAAA,MAAM,cAAA,GAAiB,YAAA,CAAa,UAAA,EAAY,WAAW,CAAA;AAE3D,IAAA,MAAM,SAAA,GAAY,CAAC,WAAA,EAAa,SAAS,EAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAEnE,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,SAAA,EAAW,SAAA,EACxB,QAAA,EAAA;AAAA,sBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,GAAA,EAAK,UAAA;AAAA,UACL,IAAA,EAAK,SAAA;AAAA,UACL,SAAA,EAAU,WAAA;AAAA,UACV,cAAA,EAAc,OAAA;AAAA,UACd,WAAA,EAAW,IAAA;AAAA,UACX,WAAA,EAAW,IAAA;AAAA,UACX,KAAA,EAAO,cAAA;AAAA,UAEN,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACT,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBAEC,SAAA,EAAU,UAAA;AAAA,gBACT,GAAG,YAAY,GAAA,CAAI,KAAA,EAAO,EAAE,QAAA,EAAU,GAAA,CAAI,UAAU,CAAA;AAAA,gBAEpD,QAAA,EAAA,GAAA,CAAI;AAAA,eAAA;AAAA,cAJA,GAAA,CAAI;AAAA,aAMZ,CAAA;AAAA,YAAA,CACC,OAAA,KAAY,MAAA,IAAU,OAAA,KAAY,OAAA,IAAW,OAAA,KAAY,MAAA,qBACzD,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EAAiB,aAAA,EAAY,MAAA,EAAO;AAAA;AAAA;AAAA,OAExD;AAAA,0BACC,KAAA,EAAA,EAAI,SAAA,EAAU,eACZ,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACT,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UAEC,SAAA,EAAU,YAAA;AAAA,UACT,GAAG,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA;AAAA,UAE1B,QAAA,EAAA,GAAA,CAAI;AAAA,SAAA;AAAA,QAJA,GAAA,CAAI;AAAA,OAMZ,CAAA,EACH;AAAA,KAAA,EACF,CAAA;AAAA,EAEJ;AACF","file":"styled.js","sourcesContent":["import {\n type CSSProperties,\n type ReactNode,\n forwardRef,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport { useTabs } from \"../useTabs\";\n\nexport type TabsVariant = \"line\" | \"solid\" | \"pill\";\nexport type TabsSize = \"sm\" | \"md\" | \"lg\";\nexport type TabsTone = \"neutral\" | \"primary\";\n\nexport interface TabItem {\n value: string;\n label: ReactNode;\n content: ReactNode;\n disabled?: boolean;\n}\n\nexport interface TabsStyledProps {\n tabs: TabItem[];\n variant?: TabsVariant;\n size?: TabsSize;\n tone?: TabsTone;\n defaultValue?: string;\n value?: string;\n onChange?: (value: string) => void;\n className?: string;\n}\n\n// Run layout effects on the client; fall back to a no-op effect on the server.\nconst useIsoLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nfunction useIndicator(\n tabListRef: React.RefObject<HTMLDivElement | null>,\n activeValue: string | undefined,\n) {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useIsoLayoutEffect(() => {\n if (!tabListRef.current || activeValue === undefined) return;\n\n const measure = () => {\n const list = tabListRef.current;\n if (!list) return;\n const activeTab = list.querySelector<HTMLElement>(\n `[aria-selected=\"true\"]`,\n );\n if (!activeTab) return;\n\n const listRect = list.getBoundingClientRect();\n const tabRect = activeTab.getBoundingClientRect();\n const x = tabRect.left - listRect.left;\n const w = tabRect.width;\n\n setStyle({\n [\"--rtab-indicator-x\" as string]: `${x}px`,\n [\"--rtab-indicator-width\" as string]: `${w}px`,\n [\"--rtab-indicator-ready\" as string]: \"1\",\n });\n };\n\n measure();\n\n if (typeof ResizeObserver === \"undefined\") return;\n const ro = new ResizeObserver(measure);\n ro.observe(tabListRef.current);\n return () => ro.disconnect();\n }, [activeValue, tabListRef]);\n\n return style;\n}\n\nexport const TabsStyled = forwardRef<HTMLDivElement, TabsStyledProps>(\n function TabsStyled(\n {\n tabs,\n variant = \"line\",\n size = \"md\",\n tone = \"neutral\",\n defaultValue,\n value,\n onChange,\n className,\n },\n ref,\n ) {\n const tabDefs = tabs.map((t) => ({ value: t.value, disabled: t.disabled }));\n\n const resolvedDefault =\n defaultValue ?? tabs.find((t) => !t.disabled)?.value;\n\n const { activeValue, getTabProps, getPanelProps } = useTabs({\n tabs: tabDefs,\n defaultValue: value === undefined ? resolvedDefault : undefined,\n value,\n onChange,\n });\n\n const tabListRef = useRef<HTMLDivElement>(null);\n const indicatorStyle = useIndicator(tabListRef, activeValue);\n\n const rootClass = [\"rtab-root\", className].filter(Boolean).join(\" \");\n\n return (\n <div ref={ref} className={rootClass}>\n <div\n ref={tabListRef}\n role=\"tablist\"\n className=\"rtab-list\"\n data-variant={variant}\n data-size={size}\n data-tone={tone}\n style={indicatorStyle}\n >\n {tabs.map((tab) => (\n <button\n key={tab.value}\n className=\"rtab-tab\"\n {...getTabProps(tab.value, { disabled: tab.disabled })}\n >\n {tab.label}\n </button>\n ))}\n {(variant === \"line\" || variant === \"solid\" || variant === \"pill\") && (\n <span className=\"rtab-indicator\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"rtab-panels\">\n {tabs.map((tab) => (\n <div\n key={tab.value}\n className=\"rtab-panel\"\n {...getPanelProps(tab.value)}\n >\n {tab.content}\n </div>\n ))}\n </div>\n </div>\n );\n },\n);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/styled/TabsStyled.tsx"],"names":["TabsStyled"],"mappings":"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","file":"styled.js","sourcesContent":["import {\n type CSSProperties,\n type ReactNode,\n forwardRef,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport {\n useTabs,\n type TabsActivation,\n type TabsChangeReason,\n type TabsOrientation,\n} from \"../useTabs\";\n\nexport type TabsVariant = \"line\" | \"solid\" | \"pill\";\nexport type TabsSize = \"sm\" | \"md\" | \"lg\";\nexport type TabsTone = \"neutral\" | \"primary\" | \"success\" | \"danger\";\n\nexport interface TabItem {\n value: string;\n label: ReactNode;\n content: ReactNode;\n disabled?: boolean;\n /** When true, renders a × button on the tab. Pair with `onTabClose`. */\n closable?: boolean;\n}\n\nexport interface TabsRenderTabContext {\n tab: TabItem;\n index: number;\n isActive: boolean;\n isDisabled: boolean;\n}\n\nexport interface TabsRenderPanelContext {\n tab: TabItem;\n isActive: boolean;\n}\n\nexport interface TabsStyledProps {\n tabs: TabItem[];\n variant?: TabsVariant;\n size?: TabsSize;\n tone?: TabsTone;\n defaultValue?: string;\n value?: string;\n /** Optional second arg reports the trigger reason (\"click\" | \"keyboard\" | \"programmatic\"). */\n onChange?: (value: string, reason: TabsChangeReason) => void;\n /** \"automatic\" (default) — arrow keys move focus AND activate. \"manual\" — arrows move focus only. */\n activation?: TabsActivation;\n /** Affects keyboard nav direction. Default \"horizontal\". */\n orientation?: TabsOrientation;\n /** Only mount panels after they've been activated at least once. Default: false (all panels mount eagerly). */\n lazyMount?: boolean;\n /** Keep all panels mounted regardless of activation (useful with `lazyMount` overrides). Default: false. */\n forceMount?: boolean;\n /** Fires when the × on a closable tab is clicked. */\n onTabClose?: (value: string) => void;\n /** When true, the tab list scrolls horizontally with chevron buttons at the edges instead of wrapping. */\n scrollable?: boolean;\n /** When true, tabs can be reordered by dragging. Fires `onReorder` with the new value order. */\n sortable?: boolean;\n onReorder?: (values: string[]) => void;\n /** When true, the active tab is scrolled into view on activation. Default: true when `scrollable`. */\n scrollActiveIntoView?: boolean;\n /** Replace the default tab button content. The button shell (a11y, ref, key) stays owned by the component. */\n renderTab?: (ctx: TabsRenderTabContext) => ReactNode;\n /** Replace the default panel rendering. */\n renderPanel?: (ctx: TabsRenderPanelContext) => ReactNode;\n className?: string;\n}\n\n// Run layout effects on the client; fall back to a no-op effect on the server.\nconst useIsoLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nfunction useIndicator(\n tabListRef: React.RefObject<HTMLDivElement | null>,\n activeValue: string | undefined,\n orientation: TabsOrientation,\n) {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useIsoLayoutEffect(() => {\n if (!tabListRef.current || activeValue === undefined) return;\n\n const measure = () => {\n const list = tabListRef.current;\n if (!list) return;\n const activeTab = list.querySelector<HTMLElement>(\n `[aria-selected=\"true\"]`,\n );\n if (!activeTab) return;\n\n const listRect = list.getBoundingClientRect();\n const tabRect = activeTab.getBoundingClientRect();\n\n if (orientation === \"vertical\") {\n const y = tabRect.top - listRect.top;\n const h = tabRect.height;\n setStyle({\n [\"--rtab-indicator-y\" as string]: `${y}px`,\n [\"--rtab-indicator-height\" as string]: `${h}px`,\n [\"--rtab-indicator-ready\" as string]: \"1\",\n });\n } else {\n const x = tabRect.left - listRect.left;\n const w = tabRect.width;\n setStyle({\n [\"--rtab-indicator-x\" as string]: `${x}px`,\n [\"--rtab-indicator-width\" as string]: `${w}px`,\n [\"--rtab-indicator-ready\" as string]: \"1\",\n });\n }\n };\n\n measure();\n\n if (typeof ResizeObserver === \"undefined\") return;\n const ro = new ResizeObserver(measure);\n ro.observe(tabListRef.current);\n return () => ro.disconnect();\n }, [activeValue, tabListRef, orientation]);\n\n return style;\n}\n\nexport const TabsStyled = forwardRef<HTMLDivElement, TabsStyledProps>(\n function TabsStyled(\n {\n tabs,\n variant = \"line\",\n size = \"md\",\n tone = \"neutral\",\n defaultValue,\n value,\n onChange,\n activation = \"automatic\",\n orientation = \"horizontal\",\n lazyMount = false,\n forceMount = false,\n onTabClose,\n scrollable = false,\n sortable = false,\n onReorder,\n scrollActiveIntoView,\n renderTab,\n renderPanel,\n className,\n },\n ref,\n ) {\n const autoScrollActive = scrollActiveIntoView ?? scrollable;\n const [scrollState, setScrollState] = useState<{ left: boolean; right: boolean }>({\n left: false,\n right: false,\n });\n const typeaheadBufferRef = useRef(\"\");\n const typeaheadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const dragValueRef = useRef<string | null>(null);\n const tabDefs = tabs.map((t) => ({ value: t.value, disabled: t.disabled }));\n\n const resolvedDefault =\n defaultValue ?? tabs.find((t) => !t.disabled)?.value;\n\n const { activeValue, getTabProps, getPanelProps } = useTabs({\n tabs: tabDefs,\n defaultValue: value === undefined ? resolvedDefault : undefined,\n value,\n onChange,\n activation,\n orientation,\n });\n\n const tabListRef = useRef<HTMLDivElement>(null);\n const indicatorStyle = useIndicator(tabListRef, activeValue, orientation);\n\n // Track which tabs have been activated for lazyMount.\n const activatedRef = useRef<Set<string>>(new Set());\n if (activeValue !== undefined) activatedRef.current.add(activeValue);\n\n const rootClass = [\"rtab-root\", className].filter(Boolean).join(\" \");\n\n // Scroll-state: detect when there's content off the left/right edges so we can\n // toggle the chevron buttons. Only relevant when scrollable.\n useEffect(() => {\n if (!scrollable) return;\n const list = tabListRef.current;\n if (!list) return;\n const update = () => {\n setScrollState({\n left: list.scrollLeft > 4,\n right: list.scrollLeft + list.clientWidth < list.scrollWidth - 4,\n });\n };\n update();\n list.addEventListener(\"scroll\", update, { passive: true });\n const ro = typeof ResizeObserver !== \"undefined\" ? new ResizeObserver(update) : null;\n ro?.observe(list);\n return () => {\n list.removeEventListener(\"scroll\", update);\n ro?.disconnect();\n };\n }, [scrollable, tabs.length]);\n\n // Auto-scroll active tab into view\n useEffect(() => {\n if (!autoScrollActive || !activeValue) return;\n const list = tabListRef.current;\n if (!list) return;\n const activeEl = list.querySelector<HTMLElement>(`[aria-selected=\"true\"]`);\n if (!activeEl) return;\n activeEl.scrollIntoView({ behavior: \"smooth\", block: \"nearest\", inline: \"nearest\" });\n }, [autoScrollActive, activeValue]);\n\n const scrollByAmount = (delta: number) => {\n tabListRef.current?.scrollBy({ left: delta, behavior: \"smooth\" });\n };\n\n // Typeahead: letters jump to the next tab whose label starts with the buffer.\n const handleTypeahead = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key.length !== 1 || e.altKey || e.ctrlKey || e.metaKey) return;\n typeaheadBufferRef.current = (typeaheadBufferRef.current + e.key).toLowerCase();\n if (typeaheadTimerRef.current) clearTimeout(typeaheadTimerRef.current);\n typeaheadTimerRef.current = setTimeout(() => { typeaheadBufferRef.current = \"\"; }, 600);\n\n const buffer = typeaheadBufferRef.current;\n const startIdx = Math.max(0, tabs.findIndex((t) => t.value === activeValue));\n // Search starting just after the active tab, wrapping around.\n for (let i = 1; i <= tabs.length; i++) {\n const idx = (startIdx + i) % tabs.length;\n const tab = tabs[idx];\n if (!tab || tab.disabled) continue;\n const labelStr = typeof tab.label === \"string\" ? tab.label : \"\";\n if (labelStr.toLowerCase().startsWith(buffer)) {\n // Use the existing tab button's click via aria-selected target.\n const list = tabListRef.current;\n const btn = list?.querySelector<HTMLButtonElement>(`[data-value=\"${tab.value}\"]`);\n btn?.click();\n btn?.focus();\n break;\n }\n }\n };\n\n // Drag-to-reorder: HTML5 DnD is fine here — no external lib.\n const handleDragStart = (val: string) => (e: React.DragEvent<HTMLButtonElement>) => {\n if (!sortable) return;\n dragValueRef.current = val;\n e.dataTransfer.effectAllowed = \"move\";\n };\n const handleDragOver = (val: string) => (e: React.DragEvent<HTMLButtonElement>) => {\n if (!sortable || !dragValueRef.current || dragValueRef.current === val) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = \"move\";\n };\n const handleDrop = (val: string) => (e: React.DragEvent<HTMLButtonElement>) => {\n if (!sortable) return;\n e.preventDefault();\n const from = dragValueRef.current;\n dragValueRef.current = null;\n if (!from || from === val || !onReorder) return;\n const order = tabs.map((t) => t.value);\n const fromIdx = order.indexOf(from);\n const toIdx = order.indexOf(val);\n if (fromIdx === -1 || toIdx === -1) return;\n const next = [...order];\n next.splice(fromIdx, 1);\n next.splice(toIdx, 0, from);\n onReorder(next);\n };\n\n return (\n <div\n ref={ref}\n className={rootClass}\n data-orientation={orientation}\n data-scrollable={scrollable || undefined}\n onKeyDown={handleTypeahead}\n >\n <div className=\"rtab-list-wrap\">\n {scrollable && scrollState.left && (\n <button\n type=\"button\"\n className=\"rtab-scroll-btn rtab-scroll-btn--left\"\n aria-label=\"Scroll left\"\n onClick={() => scrollByAmount(-200)}\n >\n <svg viewBox=\"0 0 12 12\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M7.5 3l-3 3 3 3\"/></svg>\n </button>\n )}\n <div\n ref={tabListRef}\n role=\"tablist\"\n aria-orientation={orientation}\n className=\"rtab-list\"\n data-variant={variant}\n data-size={size}\n data-tone={tone}\n data-orientation={orientation}\n data-scrollable={scrollable || undefined}\n style={indicatorStyle}\n >\n {tabs.map((tab, index) => {\n const isActive = activeValue === tab.value;\n const isDisabled = !!tab.disabled;\n const tabProps = getTabProps(tab.value, { disabled: tab.disabled });\n return (\n <button\n key={tab.value}\n data-value={tab.value}\n data-closable={tab.closable || undefined}\n className=\"rtab-tab\"\n {...tabProps}\n draggable={sortable && !isDisabled ? true : undefined}\n onDragStart={sortable ? handleDragStart(tab.value) : undefined}\n onDragOver={sortable ? handleDragOver(tab.value) : undefined}\n onDrop={sortable ? handleDrop(tab.value) : undefined}\n >\n {renderTab\n ? renderTab({ tab, index, isActive, isDisabled })\n : tab.label}\n {tab.closable && (\n <span\n className=\"rtab-close\"\n role=\"button\"\n tabIndex={-1}\n aria-label={`Close ${typeof tab.label === \"string\" ? tab.label : \"tab\"}`}\n onClick={(e) => {\n e.stopPropagation();\n onTabClose?.(tab.value);\n }}\n >\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M3 3l6 6M9 3l-6 6\"/>\n </svg>\n </span>\n )}\n </button>\n );\n })}\n {(variant === \"line\" || variant === \"solid\" || variant === \"pill\") && (\n <span className=\"rtab-indicator\" aria-hidden=\"true\" />\n )}\n </div>\n {scrollable && scrollState.right && (\n <button\n type=\"button\"\n className=\"rtab-scroll-btn rtab-scroll-btn--right\"\n aria-label=\"Scroll right\"\n onClick={() => scrollByAmount(200)}\n >\n <svg viewBox=\"0 0 12 12\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M4.5 3l3 3-3 3\"/></svg>\n </button>\n )}\n </div>\n <div className=\"rtab-panels\">\n {tabs.map((tab) => {\n const isActive = activeValue === tab.value;\n const shouldRender =\n forceMount || !lazyMount || activatedRef.current.has(tab.value);\n return (\n <div\n key={tab.value}\n className=\"rtab-panel\"\n {...getPanelProps(tab.value)}\n >\n {shouldRender\n ? renderPanel\n ? renderPanel({ tab, isActive })\n : tab.content\n : null}\n </div>\n );\n })}\n </div>\n </div>\n );\n },\n);\n"]}
|
package/dist/styles.css
CHANGED
|
@@ -296,3 +296,123 @@
|
|
|
296
296
|
transition-duration: 0ms !important;
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
|
+
|
|
300
|
+
/* ============================================================================
|
|
301
|
+
* 0.2.0 additions: vertical orientation, success/danger tones, data-state
|
|
302
|
+
* ========================================================================== */
|
|
303
|
+
.rtab-root[data-orientation="vertical"] { display: flex; gap: 1rem; align-items: flex-start; }
|
|
304
|
+
.rtab-list[data-orientation="vertical"] {
|
|
305
|
+
flex-direction: column;
|
|
306
|
+
align-items: stretch;
|
|
307
|
+
border-right: 1px solid var(--rtab-border-list);
|
|
308
|
+
border-bottom: none;
|
|
309
|
+
min-width: 160px;
|
|
310
|
+
}
|
|
311
|
+
.rtab-list[data-orientation="vertical"] .rtab-tab { text-align: left; justify-content: flex-start; }
|
|
312
|
+
.rtab-list[data-orientation="vertical"] .rtab-indicator {
|
|
313
|
+
--rtab-indicator-x: auto;
|
|
314
|
+
--rtab-indicator-width: 2px;
|
|
315
|
+
inset-inline-start: auto;
|
|
316
|
+
inset-inline-end: -1px;
|
|
317
|
+
top: 0;
|
|
318
|
+
bottom: auto;
|
|
319
|
+
width: 2px;
|
|
320
|
+
height: var(--rtab-indicator-height, 0);
|
|
321
|
+
transform: translateY(var(--rtab-indicator-y, 0));
|
|
322
|
+
}
|
|
323
|
+
.rtab-root[data-orientation="vertical"] .rtab-panels { flex: 1; }
|
|
324
|
+
|
|
325
|
+
/* Tones — success / danger */
|
|
326
|
+
.rtab-list[data-tone="success"] { --rtab-fg-active: #166534; --rtab-bg-tab-active: #dcfce7; --rtab-bg-indicator: #16a34a; --rtab-ring: rgba(34, 197, 94, 0.45); }
|
|
327
|
+
.rtab-list[data-tone="danger"] { --rtab-fg-active: #991b1b; --rtab-bg-tab-active: #fee2e2; --rtab-bg-indicator: #dc2626; --rtab-ring: rgba(239, 68, 68, 0.45); }
|
|
328
|
+
|
|
329
|
+
[data-theme="dark"] .rtab-list[data-tone="success"] { --rtab-fg-active: #bbf7d0; --rtab-bg-tab-active: #14532d; --rtab-bg-indicator: #4ade80; }
|
|
330
|
+
[data-theme="dark"] .rtab-list[data-tone="danger"] { --rtab-fg-active: #fecaca; --rtab-bg-tab-active: #7f1d1d; --rtab-bg-indicator: #f87171; }
|
|
331
|
+
|
|
332
|
+
/* ============================================================================
|
|
333
|
+
* Closeable / scrollable / sortable tabs (1.x additions)
|
|
334
|
+
* ============================================================================ */
|
|
335
|
+
|
|
336
|
+
/* Wrap that holds the list + the chevron scroll buttons */
|
|
337
|
+
.rtab-list-wrap {
|
|
338
|
+
position: relative;
|
|
339
|
+
display: flex;
|
|
340
|
+
align-items: stretch;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Scrollable mode — list itself becomes a horizontal scroll container */
|
|
344
|
+
.rtab-list[data-scrollable] {
|
|
345
|
+
overflow-x: auto;
|
|
346
|
+
scrollbar-width: none; /* Firefox */
|
|
347
|
+
scroll-behavior: smooth;
|
|
348
|
+
flex-wrap: nowrap;
|
|
349
|
+
}
|
|
350
|
+
.rtab-list[data-scrollable]::-webkit-scrollbar { display: none; }
|
|
351
|
+
.rtab-list[data-scrollable] .rtab-tab {
|
|
352
|
+
flex-shrink: 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Chevron scroll buttons */
|
|
356
|
+
.rtab-scroll-btn {
|
|
357
|
+
appearance: none;
|
|
358
|
+
background: var(--rtab-bg, transparent);
|
|
359
|
+
border: none;
|
|
360
|
+
width: 28px;
|
|
361
|
+
flex-shrink: 0;
|
|
362
|
+
display: inline-flex;
|
|
363
|
+
align-items: center;
|
|
364
|
+
justify-content: center;
|
|
365
|
+
cursor: pointer;
|
|
366
|
+
color: var(--rtab-fg-muted, currentColor);
|
|
367
|
+
opacity: 0.85;
|
|
368
|
+
transition: opacity 120ms ease, background 120ms ease;
|
|
369
|
+
}
|
|
370
|
+
.rtab-scroll-btn:hover { opacity: 1; }
|
|
371
|
+
.rtab-scroll-btn:focus-visible {
|
|
372
|
+
outline: 2px solid var(--rtab-color-active, #6366f1);
|
|
373
|
+
outline-offset: 1px;
|
|
374
|
+
}
|
|
375
|
+
.rtab-scroll-btn--left {
|
|
376
|
+
background: linear-gradient(to right, var(--rtab-bg, var(--bg, #fff)) 50%, transparent);
|
|
377
|
+
}
|
|
378
|
+
.rtab-scroll-btn--right {
|
|
379
|
+
background: linear-gradient(to left, var(--rtab-bg, var(--bg, #fff)) 50%, transparent);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* Close (×) button on closable tabs */
|
|
383
|
+
.rtab-tab[data-closable] {
|
|
384
|
+
padding-right: 0.4rem;
|
|
385
|
+
}
|
|
386
|
+
.rtab-close {
|
|
387
|
+
display: inline-flex;
|
|
388
|
+
align-items: center;
|
|
389
|
+
justify-content: center;
|
|
390
|
+
width: 16px;
|
|
391
|
+
height: 16px;
|
|
392
|
+
margin-left: 0.4rem;
|
|
393
|
+
border-radius: 3px;
|
|
394
|
+
cursor: pointer;
|
|
395
|
+
color: var(--rtab-fg-muted, currentColor);
|
|
396
|
+
opacity: 0.55;
|
|
397
|
+
transition: background 120ms ease, opacity 120ms ease;
|
|
398
|
+
}
|
|
399
|
+
.rtab-close:hover {
|
|
400
|
+
opacity: 1;
|
|
401
|
+
background: var(--rtab-close-hover-bg, rgba(0, 0, 0, 0.08));
|
|
402
|
+
}
|
|
403
|
+
[data-theme="dark"] .rtab-close:hover {
|
|
404
|
+
background: var(--rtab-close-hover-bg, rgba(255, 255, 255, 0.12));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* Drag-to-reorder visuals */
|
|
408
|
+
.rtab-tab[draggable="true"] { cursor: grab; }
|
|
409
|
+
.rtab-tab[draggable="true"]:active { cursor: grabbing; }
|
|
410
|
+
.rtab-tab[draggable="true"]:hover {
|
|
411
|
+
background: var(--rtab-tab-hover-bg, rgba(0, 0, 0, 0.04));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@media (prefers-reduced-motion: reduce) {
|
|
415
|
+
.rtab-list { scroll-behavior: auto; }
|
|
416
|
+
.rtab-scroll-btn { transition: none; }
|
|
417
|
+
.rtab-close { transition: none; }
|
|
418
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useTabs.ts"],"names":[],"mappings":"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","file":"chunk-UCRNSS7N.js","sourcesContent":["import {\n type AriaAttributes,\n type HTMLAttributes,\n type KeyboardEvent,\n useCallback,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nexport interface UseTabsTab {\n value: string;\n disabled?: boolean;\n}\n\nexport interface UseTabsOptions {\n /** Tab definitions — needed for keyboard navigation between tabs. */\n tabs?: UseTabsTab[];\n /** Controlled active value. */\n value?: string;\n /** Initial active value when uncontrolled. */\n defaultValue?: string;\n /** Called when the active tab changes. */\n onChange?: (value: string) => void;\n}\n\nexport interface TabProps extends HTMLAttributes<HTMLButtonElement> {\n id: string;\n role: \"tab\";\n \"aria-selected\": boolean;\n \"aria-controls\": string;\n \"aria-disabled\"?: boolean;\n tabIndex: number;\n}\n\nexport interface PanelProps extends HTMLAttributes<HTMLDivElement> {\n id: string;\n role: \"tabpanel\";\n \"aria-labelledby\": string;\n hidden: boolean;\n}\n\nexport interface UseTabsResult {\n /** Currently active tab value. */\n activeValue: string | undefined;\n /** Returns props to spread onto a tab trigger `<button>`. */\n getTabProps: (value: string, options?: { disabled?: boolean }) => TabProps;\n /** Returns props to spread onto a tab panel. */\n getPanelProps: (value: string) => PanelProps;\n /** Programmatically set the active tab. */\n setActiveValue: (value: string) => void;\n}\n\nconst tabId = (listId: string, value: string) => `${listId}-tab-${value}`;\nconst panelId = (listId: string, value: string) => `${listId}-panel-${value}`;\n\nlet counter = 0;\nfunction useStableId() {\n const ref = useRef<string | null>(null);\n if (ref.current === null) {\n ref.current = `rtabs-${++counter}`;\n }\n return ref.current;\n}\n\nexport function useTabs(opts: UseTabsOptions = {}): UseTabsResult {\n const { tabs = [], value: controlledValue, defaultValue, onChange } = opts;\n\n const listId = useStableId();\n const isControlled = controlledValue !== undefined;\n\n const [internalValue, setInternalValue] = useState<string | undefined>(\n defaultValue,\n );\n\n const activeValue = isControlled ? controlledValue : internalValue;\n\n const tabRefs = useRef<Map<string, HTMLButtonElement>>(new Map());\n\n const setActiveValue = useCallback(\n (next: string) => {\n const tab = tabs.find((t) => t.value === next);\n if (tab?.disabled) return;\n if (isControlled) {\n if (next !== controlledValue) onChange?.(next);\n } else {\n if (next === internalValue) return;\n setInternalValue(next);\n onChange?.(next);\n }\n },\n [tabs, isControlled, controlledValue, internalValue, onChange],\n );\n\n const focusTab = useCallback((value: string) => {\n tabRefs.current.get(value)?.focus();\n }, []);\n\n const handleKeyDown = useCallback(\n (currentValue: string) =>\n (event: KeyboardEvent<HTMLButtonElement>) => {\n const enabled = tabs.filter((t) => !t.disabled);\n if (enabled.length === 0) return;\n\n const currentIndex = enabled.findIndex((t) => t.value === currentValue);\n\n let nextValue: string | undefined;\n\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowDown\": {\n const nextIndex = (currentIndex + 1) % enabled.length;\n nextValue = enabled[nextIndex]?.value;\n break;\n }\n case \"ArrowLeft\":\n case \"ArrowUp\": {\n const prevIndex =\n (currentIndex - 1 + enabled.length) % enabled.length;\n nextValue = enabled[prevIndex]?.value;\n break;\n }\n case \"Home\": {\n nextValue = enabled[0]?.value;\n break;\n }\n case \"End\": {\n nextValue = enabled[enabled.length - 1]?.value;\n break;\n }\n default:\n return;\n }\n\n if (nextValue === undefined || nextValue === currentValue) return;\n event.preventDefault();\n setActiveValue(nextValue);\n focusTab(nextValue);\n },\n [tabs, setActiveValue, focusTab],\n );\n\n const getTabProps = useCallback(\n (value: string, options?: { disabled?: boolean }): TabProps => {\n const isDisabled =\n options?.disabled ?? tabs.find((t) => t.value === value)?.disabled;\n const isSelected = activeValue === value;\n return {\n id: tabId(listId, value),\n role: \"tab\",\n \"aria-selected\": isSelected,\n \"aria-controls\": panelId(listId, value),\n \"aria-disabled\": isDisabled || undefined,\n tabIndex: isSelected ? 0 : -1,\n disabled: isDisabled,\n ref: (node: HTMLButtonElement | null) => {\n if (node) {\n tabRefs.current.set(value, node);\n } else {\n tabRefs.current.delete(value);\n }\n },\n onClick: () => {\n if (!isDisabled) setActiveValue(value);\n },\n onKeyDown: handleKeyDown(value),\n } as TabProps & {\n disabled: boolean | undefined;\n ref: (node: HTMLButtonElement | null) => void;\n };\n },\n [activeValue, listId, tabs, setActiveValue, handleKeyDown],\n );\n\n const getPanelProps = useCallback(\n (value: string): PanelProps => {\n return {\n id: panelId(listId, value),\n role: \"tabpanel\",\n \"aria-labelledby\": tabId(listId, value),\n hidden: activeValue !== value,\n tabIndex: 0,\n };\n },\n [activeValue, listId],\n );\n\n return useMemo(\n () => ({ activeValue, getTabProps, getPanelProps, setActiveValue }),\n [activeValue, getTabProps, getPanelProps, setActiveValue],\n );\n}\n"]}
|