@mshafiqyajid/react-tabs 0.1.0 → 0.2.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 +122 -75
- package/dist/styled.cjs.map +1 -1
- package/dist/styled.d.cts +26 -2
- package/dist/styled.d.ts +26 -2
- package/dist/styled.js +82 -44
- package/dist/styled.js.map +1 -1
- package/dist/styles.css +32 -0
- package/package.json +1 -1
- package/dist/chunk-UCRNSS7N.js.map +0 -1
|
@@ -12,7 +12,14 @@ function useStableId() {
|
|
|
12
12
|
return ref.current;
|
|
13
13
|
}
|
|
14
14
|
function useTabs(opts = {}) {
|
|
15
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
tabs = [],
|
|
17
|
+
value: controlledValue,
|
|
18
|
+
defaultValue,
|
|
19
|
+
onChange,
|
|
20
|
+
activation = "automatic",
|
|
21
|
+
orientation = "horizontal"
|
|
22
|
+
} = opts;
|
|
16
23
|
const listId = useStableId();
|
|
17
24
|
const isControlled = controlledValue !== void 0;
|
|
18
25
|
const [internalValue, setInternalValue] = useState(
|
|
@@ -20,19 +27,21 @@ function useTabs(opts = {}) {
|
|
|
20
27
|
);
|
|
21
28
|
const activeValue = isControlled ? controlledValue : internalValue;
|
|
22
29
|
const tabRefs = useRef(/* @__PURE__ */ new Map());
|
|
30
|
+
const onChangeRef = useRef(onChange);
|
|
31
|
+
onChangeRef.current = onChange;
|
|
23
32
|
const setActiveValue = useCallback(
|
|
24
|
-
(next) => {
|
|
33
|
+
(next, reason = "programmatic") => {
|
|
25
34
|
const tab = tabs.find((t) => t.value === next);
|
|
26
35
|
if (tab?.disabled) return;
|
|
27
36
|
if (isControlled) {
|
|
28
|
-
if (next !== controlledValue)
|
|
37
|
+
if (next !== controlledValue) onChangeRef.current?.(next, reason);
|
|
29
38
|
} else {
|
|
30
39
|
if (next === internalValue) return;
|
|
31
40
|
setInternalValue(next);
|
|
32
|
-
|
|
41
|
+
onChangeRef.current?.(next, reason);
|
|
33
42
|
}
|
|
34
43
|
},
|
|
35
|
-
[tabs, isControlled, controlledValue, internalValue
|
|
44
|
+
[tabs, isControlled, controlledValue, internalValue]
|
|
36
45
|
);
|
|
37
46
|
const focusTab = useCallback((value) => {
|
|
38
47
|
tabRefs.current.get(value)?.focus();
|
|
@@ -42,37 +51,34 @@ function useTabs(opts = {}) {
|
|
|
42
51
|
const enabled = tabs.filter((t) => !t.disabled);
|
|
43
52
|
if (enabled.length === 0) return;
|
|
44
53
|
const currentIndex = enabled.findIndex((t) => t.value === currentValue);
|
|
54
|
+
const isForward = orientation === "vertical" ? event.key === "ArrowDown" : event.key === "ArrowRight";
|
|
55
|
+
const isBackward = orientation === "vertical" ? event.key === "ArrowUp" : event.key === "ArrowLeft";
|
|
45
56
|
let nextValue;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
case "End": {
|
|
64
|
-
nextValue = enabled[enabled.length - 1]?.value;
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
default:
|
|
68
|
-
return;
|
|
57
|
+
if (isForward) {
|
|
58
|
+
const nextIndex = (currentIndex + 1) % enabled.length;
|
|
59
|
+
nextValue = enabled[nextIndex]?.value;
|
|
60
|
+
} else if (isBackward) {
|
|
61
|
+
const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;
|
|
62
|
+
nextValue = enabled[prevIndex]?.value;
|
|
63
|
+
} else if (event.key === "Home") {
|
|
64
|
+
nextValue = enabled[0]?.value;
|
|
65
|
+
} else if (event.key === "End") {
|
|
66
|
+
nextValue = enabled[enabled.length - 1]?.value;
|
|
67
|
+
} else if (activation === "manual" && (event.key === "Enter" || event.key === " ")) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
setActiveValue(currentValue, "keyboard");
|
|
70
|
+
return;
|
|
71
|
+
} else {
|
|
72
|
+
return;
|
|
69
73
|
}
|
|
70
74
|
if (nextValue === void 0 || nextValue === currentValue) return;
|
|
71
75
|
event.preventDefault();
|
|
72
|
-
setActiveValue(nextValue);
|
|
73
76
|
focusTab(nextValue);
|
|
77
|
+
if (activation === "automatic") {
|
|
78
|
+
setActiveValue(nextValue, "keyboard");
|
|
79
|
+
}
|
|
74
80
|
},
|
|
75
|
-
[tabs, setActiveValue, focusTab]
|
|
81
|
+
[tabs, orientation, activation, setActiveValue, focusTab]
|
|
76
82
|
);
|
|
77
83
|
const getTabProps = useCallback(
|
|
78
84
|
(value, options) => {
|
|
@@ -84,6 +90,7 @@ function useTabs(opts = {}) {
|
|
|
84
90
|
"aria-selected": isSelected,
|
|
85
91
|
"aria-controls": panelId(listId, value),
|
|
86
92
|
"aria-disabled": isDisabled || void 0,
|
|
93
|
+
"data-state": isSelected ? "active" : "inactive",
|
|
87
94
|
tabIndex: isSelected ? 0 : -1,
|
|
88
95
|
disabled: isDisabled,
|
|
89
96
|
ref: (node) => {
|
|
@@ -94,7 +101,7 @@ function useTabs(opts = {}) {
|
|
|
94
101
|
}
|
|
95
102
|
},
|
|
96
103
|
onClick: () => {
|
|
97
|
-
if (!isDisabled) setActiveValue(value);
|
|
104
|
+
if (!isDisabled) setActiveValue(value, "click");
|
|
98
105
|
},
|
|
99
106
|
onKeyDown: handleKeyDown(value)
|
|
100
107
|
};
|
|
@@ -103,11 +110,13 @@ function useTabs(opts = {}) {
|
|
|
103
110
|
);
|
|
104
111
|
const getPanelProps = useCallback(
|
|
105
112
|
(value) => {
|
|
113
|
+
const isActive = activeValue === value;
|
|
106
114
|
return {
|
|
107
115
|
id: panelId(listId, value),
|
|
108
116
|
role: "tabpanel",
|
|
109
117
|
"aria-labelledby": tabId(listId, value),
|
|
110
|
-
|
|
118
|
+
"data-state": isActive ? "active" : "inactive",
|
|
119
|
+
hidden: !isActive,
|
|
111
120
|
tabIndex: 0
|
|
112
121
|
};
|
|
113
122
|
},
|
|
@@ -120,5 +129,5 @@ function useTabs(opts = {}) {
|
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
export { useTabs };
|
|
123
|
-
//# sourceMappingURL=chunk-
|
|
124
|
-
//# sourceMappingURL=chunk-
|
|
132
|
+
//# sourceMappingURL=chunk-NNDW3W6L.js.map
|
|
133
|
+
//# sourceMappingURL=chunk-NNDW3W6L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useTabs.ts"],"names":[],"mappings":"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","file":"chunk-NNDW3W6L.js","sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n useCallback,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nexport type TabsActivation = \"automatic\" | \"manual\";\nexport type TabsOrientation = \"horizontal\" | \"vertical\";\nexport type TabsChangeReason = \"click\" | \"keyboard\" | \"programmatic\";\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. Optional second arg reports the trigger reason. */\n onChange?: (value: string, reason: TabsChangeReason) => void;\n /** \"automatic\" (default) — arrow keys move focus AND activate. \"manual\" — arrows move focus only; Enter/Space activates. */\n activation?: TabsActivation;\n /** Affects keyboard nav. Default \"horizontal\". */\n orientation?: TabsOrientation;\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 \"data-state\": \"active\" | \"inactive\";\n tabIndex: number;\n}\n\nexport interface PanelProps extends HTMLAttributes<HTMLDivElement> {\n id: string;\n role: \"tabpanel\";\n \"aria-labelledby\": string;\n \"data-state\": \"active\" | \"inactive\";\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 {\n tabs = [],\n value: controlledValue,\n defaultValue,\n onChange,\n activation = \"automatic\",\n orientation = \"horizontal\",\n } = 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 const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const setActiveValue = useCallback(\n (next: string, reason: TabsChangeReason = \"programmatic\") => {\n const tab = tabs.find((t) => t.value === next);\n if (tab?.disabled) return;\n if (isControlled) {\n if (next !== controlledValue) onChangeRef.current?.(next, reason);\n } else {\n if (next === internalValue) return;\n setInternalValue(next);\n onChangeRef.current?.(next, reason);\n }\n },\n [tabs, isControlled, controlledValue, internalValue],\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 const isForward =\n orientation === \"vertical\"\n ? event.key === \"ArrowDown\"\n : event.key === \"ArrowRight\";\n const isBackward =\n orientation === \"vertical\"\n ? event.key === \"ArrowUp\"\n : event.key === \"ArrowLeft\";\n\n let nextValue: string | undefined;\n\n if (isForward) {\n const nextIndex = (currentIndex + 1) % enabled.length;\n nextValue = enabled[nextIndex]?.value;\n } else if (isBackward) {\n const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;\n nextValue = enabled[prevIndex]?.value;\n } else if (event.key === \"Home\") {\n nextValue = enabled[0]?.value;\n } else if (event.key === \"End\") {\n nextValue = enabled[enabled.length - 1]?.value;\n } else if (\n activation === \"manual\" &&\n (event.key === \"Enter\" || event.key === \" \")\n ) {\n event.preventDefault();\n setActiveValue(currentValue, \"keyboard\");\n return;\n } else {\n return;\n }\n\n if (nextValue === undefined || nextValue === currentValue) return;\n event.preventDefault();\n focusTab(nextValue);\n if (activation === \"automatic\") {\n setActiveValue(nextValue, \"keyboard\");\n }\n },\n [tabs, orientation, activation, 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 \"data-state\": isSelected ? \"active\" : \"inactive\",\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, \"click\");\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 const isActive = activeValue === value;\n return {\n id: panelId(listId, value),\n role: \"tabpanel\",\n \"aria-labelledby\": tabId(listId, value),\n \"data-state\": isActive ? \"active\" : \"inactive\",\n hidden: !isActive,\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"]}
|
package/dist/index.cjs
CHANGED
|
@@ -14,7 +14,14 @@ function useStableId() {
|
|
|
14
14
|
return ref.current;
|
|
15
15
|
}
|
|
16
16
|
function useTabs(opts = {}) {
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
tabs = [],
|
|
19
|
+
value: controlledValue,
|
|
20
|
+
defaultValue,
|
|
21
|
+
onChange,
|
|
22
|
+
activation = "automatic",
|
|
23
|
+
orientation = "horizontal"
|
|
24
|
+
} = opts;
|
|
18
25
|
const listId = useStableId();
|
|
19
26
|
const isControlled = controlledValue !== void 0;
|
|
20
27
|
const [internalValue, setInternalValue] = react.useState(
|
|
@@ -22,19 +29,21 @@ function useTabs(opts = {}) {
|
|
|
22
29
|
);
|
|
23
30
|
const activeValue = isControlled ? controlledValue : internalValue;
|
|
24
31
|
const tabRefs = react.useRef(/* @__PURE__ */ new Map());
|
|
32
|
+
const onChangeRef = react.useRef(onChange);
|
|
33
|
+
onChangeRef.current = onChange;
|
|
25
34
|
const setActiveValue = react.useCallback(
|
|
26
|
-
(next) => {
|
|
35
|
+
(next, reason = "programmatic") => {
|
|
27
36
|
const tab = tabs.find((t) => t.value === next);
|
|
28
37
|
if (tab?.disabled) return;
|
|
29
38
|
if (isControlled) {
|
|
30
|
-
if (next !== controlledValue)
|
|
39
|
+
if (next !== controlledValue) onChangeRef.current?.(next, reason);
|
|
31
40
|
} else {
|
|
32
41
|
if (next === internalValue) return;
|
|
33
42
|
setInternalValue(next);
|
|
34
|
-
|
|
43
|
+
onChangeRef.current?.(next, reason);
|
|
35
44
|
}
|
|
36
45
|
},
|
|
37
|
-
[tabs, isControlled, controlledValue, internalValue
|
|
46
|
+
[tabs, isControlled, controlledValue, internalValue]
|
|
38
47
|
);
|
|
39
48
|
const focusTab = react.useCallback((value) => {
|
|
40
49
|
tabRefs.current.get(value)?.focus();
|
|
@@ -44,37 +53,34 @@ function useTabs(opts = {}) {
|
|
|
44
53
|
const enabled = tabs.filter((t) => !t.disabled);
|
|
45
54
|
if (enabled.length === 0) return;
|
|
46
55
|
const currentIndex = enabled.findIndex((t) => t.value === currentValue);
|
|
56
|
+
const isForward = orientation === "vertical" ? event.key === "ArrowDown" : event.key === "ArrowRight";
|
|
57
|
+
const isBackward = orientation === "vertical" ? event.key === "ArrowUp" : event.key === "ArrowLeft";
|
|
47
58
|
let nextValue;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
case "End": {
|
|
66
|
-
nextValue = enabled[enabled.length - 1]?.value;
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
default:
|
|
70
|
-
return;
|
|
59
|
+
if (isForward) {
|
|
60
|
+
const nextIndex = (currentIndex + 1) % enabled.length;
|
|
61
|
+
nextValue = enabled[nextIndex]?.value;
|
|
62
|
+
} else if (isBackward) {
|
|
63
|
+
const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;
|
|
64
|
+
nextValue = enabled[prevIndex]?.value;
|
|
65
|
+
} else if (event.key === "Home") {
|
|
66
|
+
nextValue = enabled[0]?.value;
|
|
67
|
+
} else if (event.key === "End") {
|
|
68
|
+
nextValue = enabled[enabled.length - 1]?.value;
|
|
69
|
+
} else if (activation === "manual" && (event.key === "Enter" || event.key === " ")) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
setActiveValue(currentValue, "keyboard");
|
|
72
|
+
return;
|
|
73
|
+
} else {
|
|
74
|
+
return;
|
|
71
75
|
}
|
|
72
76
|
if (nextValue === void 0 || nextValue === currentValue) return;
|
|
73
77
|
event.preventDefault();
|
|
74
|
-
setActiveValue(nextValue);
|
|
75
78
|
focusTab(nextValue);
|
|
79
|
+
if (activation === "automatic") {
|
|
80
|
+
setActiveValue(nextValue, "keyboard");
|
|
81
|
+
}
|
|
76
82
|
},
|
|
77
|
-
[tabs, setActiveValue, focusTab]
|
|
83
|
+
[tabs, orientation, activation, setActiveValue, focusTab]
|
|
78
84
|
);
|
|
79
85
|
const getTabProps = react.useCallback(
|
|
80
86
|
(value, options) => {
|
|
@@ -86,6 +92,7 @@ function useTabs(opts = {}) {
|
|
|
86
92
|
"aria-selected": isSelected,
|
|
87
93
|
"aria-controls": panelId(listId, value),
|
|
88
94
|
"aria-disabled": isDisabled || void 0,
|
|
95
|
+
"data-state": isSelected ? "active" : "inactive",
|
|
89
96
|
tabIndex: isSelected ? 0 : -1,
|
|
90
97
|
disabled: isDisabled,
|
|
91
98
|
ref: (node) => {
|
|
@@ -96,7 +103,7 @@ function useTabs(opts = {}) {
|
|
|
96
103
|
}
|
|
97
104
|
},
|
|
98
105
|
onClick: () => {
|
|
99
|
-
if (!isDisabled) setActiveValue(value);
|
|
106
|
+
if (!isDisabled) setActiveValue(value, "click");
|
|
100
107
|
},
|
|
101
108
|
onKeyDown: handleKeyDown(value)
|
|
102
109
|
};
|
|
@@ -105,11 +112,13 @@ function useTabs(opts = {}) {
|
|
|
105
112
|
);
|
|
106
113
|
const getPanelProps = react.useCallback(
|
|
107
114
|
(value) => {
|
|
115
|
+
const isActive = activeValue === value;
|
|
108
116
|
return {
|
|
109
117
|
id: panelId(listId, value),
|
|
110
118
|
role: "tabpanel",
|
|
111
119
|
"aria-labelledby": tabId(listId, value),
|
|
112
|
-
|
|
120
|
+
"data-state": isActive ? "active" : "inactive",
|
|
121
|
+
hidden: !isActive,
|
|
113
122
|
tabIndex: 0
|
|
114
123
|
};
|
|
115
124
|
},
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useTabs.ts"],"names":["useRef","useState","useCallback","useMemo"],"mappings":"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","file":"index.cjs","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/useTabs.ts"],"names":["useRef","useState","useCallback","useMemo"],"mappings":";;;;;AA8DA,IAAM,QAAQ,CAAC,MAAA,EAAgB,UAAkB,CAAA,EAAG,MAAM,QAAQ,KAAK,CAAA,CAAA;AACvE,IAAM,UAAU,CAAC,MAAA,EAAgB,UAAkB,CAAA,EAAG,MAAM,UAAU,KAAK,CAAA,CAAA;AAE3E,IAAI,OAAA,GAAU,CAAA;AACd,SAAS,WAAA,GAAc;AACrB,EAAA,MAAM,GAAA,GAAMA,aAAsB,IAAI,CAAA;AACtC,EAAA,IAAI,GAAA,CAAI,YAAY,IAAA,EAAM;AACxB,IAAA,GAAA,CAAI,OAAA,GAAU,CAAA,MAAA,EAAS,EAAE,OAAO,CAAA,CAAA;AAAA,EAClC;AACA,EAAA,OAAO,GAAA,CAAI,OAAA;AACb;AAEO,SAAS,OAAA,CAAQ,IAAA,GAAuB,EAAC,EAAkB;AAChE,EAAA,MAAM;AAAA,IACJ,OAAO,EAAC;AAAA,IACR,KAAA,EAAO,eAAA;AAAA,IACP,YAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA,GAAa,WAAA;AAAA,IACb,WAAA,GAAc;AAAA,GAChB,GAAI,IAAA;AAEJ,EAAA,MAAM,SAAS,WAAA,EAAY;AAC3B,EAAA,MAAM,eAAe,eAAA,KAAoB,MAAA;AAEzC,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIC,cAAA;AAAA,IACxC;AAAA,GACF;AAEA,EAAA,MAAM,WAAA,GAAc,eAAe,eAAA,GAAkB,aAAA;AAErD,EAAA,MAAM,OAAA,GAAUD,YAAA,iBAAuC,IAAI,GAAA,EAAK,CAAA;AAChE,EAAA,MAAM,WAAA,GAAcA,aAAO,QAAQ,CAAA;AACnC,EAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AAEtB,EAAA,MAAM,cAAA,GAAiBE,iBAAA;AAAA,IACrB,CAAC,IAAA,EAAc,MAAA,GAA2B,cAAA,KAAmB;AAC3D,MAAA,MAAM,MAAM,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,IAAI,CAAA;AAC7C,MAAA,IAAI,KAAK,QAAA,EAAU;AACnB,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,IAAI,IAAA,KAAS,eAAA,EAAiB,WAAA,CAAY,OAAA,GAAU,MAAM,MAAM,CAAA;AAAA,MAClE,CAAA,MAAO;AACL,QAAA,IAAI,SAAS,aAAA,EAAe;AAC5B,QAAA,gBAAA,CAAiB,IAAI,CAAA;AACrB,QAAA,WAAA,CAAY,OAAA,GAAU,MAAM,MAAM,CAAA;AAAA,MACpC;AAAA,IACF,CAAA;AAAA,IACA,CAAC,IAAA,EAAM,YAAA,EAAc,eAAA,EAAiB,aAAa;AAAA,GACrD;AAEA,EAAA,MAAM,QAAA,GAAWA,iBAAA,CAAY,CAAC,KAAA,KAAkB;AAC9C,IAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA,EAAG,KAAA,EAAM;AAAA,EACpC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,iBAAA;AAAA,IACpB,CAAC,YAAA,KACC,CAAC,KAAA,KAA4C;AAC3C,MAAA,MAAM,UAAU,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,EAAE,QAAQ,CAAA;AAC9C,MAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AAE1B,MAAA,MAAM,eAAe,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,YAAY,CAAA;AAEtE,MAAA,MAAM,YACJ,WAAA,KAAgB,UAAA,GACZ,MAAM,GAAA,KAAQ,WAAA,GACd,MAAM,GAAA,KAAQ,YAAA;AACpB,MAAA,MAAM,aACJ,WAAA,KAAgB,UAAA,GACZ,MAAM,GAAA,KAAQ,SAAA,GACd,MAAM,GAAA,KAAQ,WAAA;AAEpB,MAAA,IAAI,SAAA;AAEJ,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,MAAM,SAAA,GAAA,CAAa,YAAA,GAAe,CAAA,IAAK,OAAA,CAAQ,MAAA;AAC/C,QAAA,SAAA,GAAY,OAAA,CAAQ,SAAS,CAAA,EAAG,KAAA;AAAA,MAClC,WAAW,UAAA,EAAY;AACrB,QAAA,MAAM,SAAA,GAAA,CAAa,YAAA,GAAe,CAAA,GAAI,OAAA,CAAQ,UAAU,OAAA,CAAQ,MAAA;AAChE,QAAA,SAAA,GAAY,OAAA,CAAQ,SAAS,CAAA,EAAG,KAAA;AAAA,MAClC,CAAA,MAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,MAAA,EAAQ;AAC/B,QAAA,SAAA,GAAY,OAAA,CAAQ,CAAC,CAAA,EAAG,KAAA;AAAA,MAC1B,CAAA,MAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,KAAA,EAAO;AAC9B,QAAA,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,EAAG,KAAA;AAAA,MAC3C,CAAA,MAAA,IACE,eAAe,QAAA,KACd,KAAA,CAAM,QAAQ,OAAA,IAAW,KAAA,CAAM,QAAQ,GAAA,CAAA,EACxC;AACA,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,cAAA,CAAe,cAAc,UAAU,CAAA;AACvC,QAAA;AAAA,MACF,CAAA,MAAO;AACL,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,SAAA,KAAc,MAAA,IAAa,SAAA,KAAc,YAAA,EAAc;AAC3D,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,QAAA,CAAS,SAAS,CAAA;AAClB,MAAA,IAAI,eAAe,WAAA,EAAa;AAC9B,QAAA,cAAA,CAAe,WAAW,UAAU,CAAA;AAAA,MACtC;AAAA,IACF,CAAA;AAAA,IACF,CAAC,IAAA,EAAM,WAAA,EAAa,UAAA,EAAY,gBAAgB,QAAQ;AAAA,GAC1D;AAEA,EAAA,MAAM,WAAA,GAAcA,iBAAA;AAAA,IAClB,CAAC,OAAe,OAAA,KAA+C;AAC7D,MAAA,MAAM,UAAA,GACJ,OAAA,EAAS,QAAA,IAAY,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,KAAU,KAAK,CAAA,EAAG,QAAA;AAC5D,MAAA,MAAM,aAAa,WAAA,KAAgB,KAAA;AACnC,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA;AAAA,QACvB,IAAA,EAAM,KAAA;AAAA,QACN,eAAA,EAAiB,UAAA;AAAA,QACjB,eAAA,EAAiB,OAAA,CAAQ,MAAA,EAAQ,KAAK,CAAA;AAAA,QACtC,iBAAiB,UAAA,IAAc,MAAA;AAAA,QAC/B,YAAA,EAAc,aAAa,QAAA,GAAW,UAAA;AAAA,QACtC,QAAA,EAAU,aAAa,CAAA,GAAI,EAAA;AAAA,QAC3B,QAAA,EAAU,UAAA;AAAA,QACV,GAAA,EAAK,CAAC,IAAA,KAAmC;AACvC,UAAA,IAAI,IAAA,EAAM;AACR,YAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,KAAA,EAAO,IAAI,CAAA;AAAA,UACjC,CAAA,MAAO;AACL,YAAA,OAAA,CAAQ,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,UAC9B;AAAA,QACF,CAAA;AAAA,QACA,SAAS,MAAM;AACb,UAAA,IAAI,CAAC,UAAA,EAAY,cAAA,CAAe,KAAA,EAAO,OAAO,CAAA;AAAA,QAChD,CAAA;AAAA,QACA,SAAA,EAAW,cAAc,KAAK;AAAA,OAChC;AAAA,IAIF,CAAA;AAAA,IACA,CAAC,WAAA,EAAa,MAAA,EAAQ,IAAA,EAAM,gBAAgB,aAAa;AAAA,GAC3D;AAEA,EAAA,MAAM,aAAA,GAAgBA,iBAAA;AAAA,IACpB,CAAC,KAAA,KAA8B;AAC7B,MAAA,MAAM,WAAW,WAAA,KAAgB,KAAA;AACjC,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,OAAA,CAAQ,MAAA,EAAQ,KAAK,CAAA;AAAA,QACzB,IAAA,EAAM,UAAA;AAAA,QACN,iBAAA,EAAmB,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA;AAAA,QACtC,YAAA,EAAc,WAAW,QAAA,GAAW,UAAA;AAAA,QACpC,QAAQ,CAAC,QAAA;AAAA,QACT,QAAA,EAAU;AAAA,OACZ;AAAA,IACF,CAAA;AAAA,IACA,CAAC,aAAa,MAAM;AAAA,GACtB;AAEA,EAAA,OAAOC,aAAA;AAAA,IACL,OAAO,EAAE,WAAA,EAAa,WAAA,EAAa,eAAe,cAAA,EAAe,CAAA;AAAA,IACjE,CAAC,WAAA,EAAa,WAAA,EAAa,aAAA,EAAe,cAAc;AAAA,GAC1D;AACF","file":"index.cjs","sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n useCallback,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nexport type TabsActivation = \"automatic\" | \"manual\";\nexport type TabsOrientation = \"horizontal\" | \"vertical\";\nexport type TabsChangeReason = \"click\" | \"keyboard\" | \"programmatic\";\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. Optional second arg reports the trigger reason. */\n onChange?: (value: string, reason: TabsChangeReason) => void;\n /** \"automatic\" (default) — arrow keys move focus AND activate. \"manual\" — arrows move focus only; Enter/Space activates. */\n activation?: TabsActivation;\n /** Affects keyboard nav. Default \"horizontal\". */\n orientation?: TabsOrientation;\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 \"data-state\": \"active\" | \"inactive\";\n tabIndex: number;\n}\n\nexport interface PanelProps extends HTMLAttributes<HTMLDivElement> {\n id: string;\n role: \"tabpanel\";\n \"aria-labelledby\": string;\n \"data-state\": \"active\" | \"inactive\";\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 {\n tabs = [],\n value: controlledValue,\n defaultValue,\n onChange,\n activation = \"automatic\",\n orientation = \"horizontal\",\n } = 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 const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const setActiveValue = useCallback(\n (next: string, reason: TabsChangeReason = \"programmatic\") => {\n const tab = tabs.find((t) => t.value === next);\n if (tab?.disabled) return;\n if (isControlled) {\n if (next !== controlledValue) onChangeRef.current?.(next, reason);\n } else {\n if (next === internalValue) return;\n setInternalValue(next);\n onChangeRef.current?.(next, reason);\n }\n },\n [tabs, isControlled, controlledValue, internalValue],\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 const isForward =\n orientation === \"vertical\"\n ? event.key === \"ArrowDown\"\n : event.key === \"ArrowRight\";\n const isBackward =\n orientation === \"vertical\"\n ? event.key === \"ArrowUp\"\n : event.key === \"ArrowLeft\";\n\n let nextValue: string | undefined;\n\n if (isForward) {\n const nextIndex = (currentIndex + 1) % enabled.length;\n nextValue = enabled[nextIndex]?.value;\n } else if (isBackward) {\n const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;\n nextValue = enabled[prevIndex]?.value;\n } else if (event.key === \"Home\") {\n nextValue = enabled[0]?.value;\n } else if (event.key === \"End\") {\n nextValue = enabled[enabled.length - 1]?.value;\n } else if (\n activation === \"manual\" &&\n (event.key === \"Enter\" || event.key === \" \")\n ) {\n event.preventDefault();\n setActiveValue(currentValue, \"keyboard\");\n return;\n } else {\n return;\n }\n\n if (nextValue === undefined || nextValue === currentValue) return;\n event.preventDefault();\n focusTab(nextValue);\n if (activation === \"automatic\") {\n setActiveValue(nextValue, \"keyboard\");\n }\n },\n [tabs, orientation, activation, 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 \"data-state\": isSelected ? \"active\" : \"inactive\",\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, \"click\");\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 const isActive = activeValue === value;\n return {\n id: panelId(listId, value),\n role: \"tabpanel\",\n \"aria-labelledby\": tabId(listId, value),\n \"data-state\": isActive ? \"active\" : \"inactive\",\n hidden: !isActive,\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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
2
|
|
|
3
|
+
type TabsActivation = "automatic" | "manual";
|
|
4
|
+
type TabsOrientation = "horizontal" | "vertical";
|
|
5
|
+
type TabsChangeReason = "click" | "keyboard" | "programmatic";
|
|
3
6
|
interface UseTabsTab {
|
|
4
7
|
value: string;
|
|
5
8
|
disabled?: boolean;
|
|
@@ -11,8 +14,12 @@ interface UseTabsOptions {
|
|
|
11
14
|
value?: string;
|
|
12
15
|
/** Initial active value when uncontrolled. */
|
|
13
16
|
defaultValue?: string;
|
|
14
|
-
/** Called when the active tab changes. */
|
|
15
|
-
onChange?: (value: string) => void;
|
|
17
|
+
/** Called when the active tab changes. Optional second arg reports the trigger reason. */
|
|
18
|
+
onChange?: (value: string, reason: TabsChangeReason) => void;
|
|
19
|
+
/** "automatic" (default) — arrow keys move focus AND activate. "manual" — arrows move focus only; Enter/Space activates. */
|
|
20
|
+
activation?: TabsActivation;
|
|
21
|
+
/** Affects keyboard nav. Default "horizontal". */
|
|
22
|
+
orientation?: TabsOrientation;
|
|
16
23
|
}
|
|
17
24
|
interface TabProps extends HTMLAttributes<HTMLButtonElement> {
|
|
18
25
|
id: string;
|
|
@@ -20,12 +27,14 @@ interface TabProps extends HTMLAttributes<HTMLButtonElement> {
|
|
|
20
27
|
"aria-selected": boolean;
|
|
21
28
|
"aria-controls": string;
|
|
22
29
|
"aria-disabled"?: boolean;
|
|
30
|
+
"data-state": "active" | "inactive";
|
|
23
31
|
tabIndex: number;
|
|
24
32
|
}
|
|
25
33
|
interface PanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
26
34
|
id: string;
|
|
27
35
|
role: "tabpanel";
|
|
28
36
|
"aria-labelledby": string;
|
|
37
|
+
"data-state": "active" | "inactive";
|
|
29
38
|
hidden: boolean;
|
|
30
39
|
}
|
|
31
40
|
interface UseTabsResult {
|
|
@@ -42,4 +51,4 @@ interface UseTabsResult {
|
|
|
42
51
|
}
|
|
43
52
|
declare function useTabs(opts?: UseTabsOptions): UseTabsResult;
|
|
44
53
|
|
|
45
|
-
export { type PanelProps, type TabProps, type UseTabsOptions, type UseTabsResult, type UseTabsTab, useTabs };
|
|
54
|
+
export { type PanelProps, type TabProps, type TabsActivation, type TabsChangeReason, type TabsOrientation, type UseTabsOptions, type UseTabsResult, type UseTabsTab, useTabs };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
2
|
|
|
3
|
+
type TabsActivation = "automatic" | "manual";
|
|
4
|
+
type TabsOrientation = "horizontal" | "vertical";
|
|
5
|
+
type TabsChangeReason = "click" | "keyboard" | "programmatic";
|
|
3
6
|
interface UseTabsTab {
|
|
4
7
|
value: string;
|
|
5
8
|
disabled?: boolean;
|
|
@@ -11,8 +14,12 @@ interface UseTabsOptions {
|
|
|
11
14
|
value?: string;
|
|
12
15
|
/** Initial active value when uncontrolled. */
|
|
13
16
|
defaultValue?: string;
|
|
14
|
-
/** Called when the active tab changes. */
|
|
15
|
-
onChange?: (value: string) => void;
|
|
17
|
+
/** Called when the active tab changes. Optional second arg reports the trigger reason. */
|
|
18
|
+
onChange?: (value: string, reason: TabsChangeReason) => void;
|
|
19
|
+
/** "automatic" (default) — arrow keys move focus AND activate. "manual" — arrows move focus only; Enter/Space activates. */
|
|
20
|
+
activation?: TabsActivation;
|
|
21
|
+
/** Affects keyboard nav. Default "horizontal". */
|
|
22
|
+
orientation?: TabsOrientation;
|
|
16
23
|
}
|
|
17
24
|
interface TabProps extends HTMLAttributes<HTMLButtonElement> {
|
|
18
25
|
id: string;
|
|
@@ -20,12 +27,14 @@ interface TabProps extends HTMLAttributes<HTMLButtonElement> {
|
|
|
20
27
|
"aria-selected": boolean;
|
|
21
28
|
"aria-controls": string;
|
|
22
29
|
"aria-disabled"?: boolean;
|
|
30
|
+
"data-state": "active" | "inactive";
|
|
23
31
|
tabIndex: number;
|
|
24
32
|
}
|
|
25
33
|
interface PanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
26
34
|
id: string;
|
|
27
35
|
role: "tabpanel";
|
|
28
36
|
"aria-labelledby": string;
|
|
37
|
+
"data-state": "active" | "inactive";
|
|
29
38
|
hidden: boolean;
|
|
30
39
|
}
|
|
31
40
|
interface UseTabsResult {
|
|
@@ -42,4 +51,4 @@ interface UseTabsResult {
|
|
|
42
51
|
}
|
|
43
52
|
declare function useTabs(opts?: UseTabsOptions): UseTabsResult;
|
|
44
53
|
|
|
45
|
-
export { type PanelProps, type TabProps, type UseTabsOptions, type UseTabsResult, type UseTabsTab, useTabs };
|
|
54
|
+
export { type PanelProps, type TabProps, type TabsActivation, type TabsChangeReason, type TabsOrientation, type UseTabsOptions, type UseTabsResult, type UseTabsTab, useTabs };
|
package/dist/index.js
CHANGED
package/dist/styled.cjs
CHANGED
|
@@ -15,7 +15,14 @@ function useStableId() {
|
|
|
15
15
|
return ref.current;
|
|
16
16
|
}
|
|
17
17
|
function useTabs(opts = {}) {
|
|
18
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
tabs = [],
|
|
20
|
+
value: controlledValue,
|
|
21
|
+
defaultValue,
|
|
22
|
+
onChange,
|
|
23
|
+
activation = "automatic",
|
|
24
|
+
orientation = "horizontal"
|
|
25
|
+
} = opts;
|
|
19
26
|
const listId = useStableId();
|
|
20
27
|
const isControlled = controlledValue !== void 0;
|
|
21
28
|
const [internalValue, setInternalValue] = react.useState(
|
|
@@ -23,19 +30,21 @@ function useTabs(opts = {}) {
|
|
|
23
30
|
);
|
|
24
31
|
const activeValue = isControlled ? controlledValue : internalValue;
|
|
25
32
|
const tabRefs = react.useRef(/* @__PURE__ */ new Map());
|
|
33
|
+
const onChangeRef = react.useRef(onChange);
|
|
34
|
+
onChangeRef.current = onChange;
|
|
26
35
|
const setActiveValue = react.useCallback(
|
|
27
|
-
(next) => {
|
|
36
|
+
(next, reason = "programmatic") => {
|
|
28
37
|
const tab = tabs.find((t) => t.value === next);
|
|
29
38
|
if (tab?.disabled) return;
|
|
30
39
|
if (isControlled) {
|
|
31
|
-
if (next !== controlledValue)
|
|
40
|
+
if (next !== controlledValue) onChangeRef.current?.(next, reason);
|
|
32
41
|
} else {
|
|
33
42
|
if (next === internalValue) return;
|
|
34
43
|
setInternalValue(next);
|
|
35
|
-
|
|
44
|
+
onChangeRef.current?.(next, reason);
|
|
36
45
|
}
|
|
37
46
|
},
|
|
38
|
-
[tabs, isControlled, controlledValue, internalValue
|
|
47
|
+
[tabs, isControlled, controlledValue, internalValue]
|
|
39
48
|
);
|
|
40
49
|
const focusTab = react.useCallback((value) => {
|
|
41
50
|
tabRefs.current.get(value)?.focus();
|
|
@@ -45,37 +54,34 @@ function useTabs(opts = {}) {
|
|
|
45
54
|
const enabled = tabs.filter((t) => !t.disabled);
|
|
46
55
|
if (enabled.length === 0) return;
|
|
47
56
|
const currentIndex = enabled.findIndex((t) => t.value === currentValue);
|
|
57
|
+
const isForward = orientation === "vertical" ? event.key === "ArrowDown" : event.key === "ArrowRight";
|
|
58
|
+
const isBackward = orientation === "vertical" ? event.key === "ArrowUp" : event.key === "ArrowLeft";
|
|
48
59
|
let nextValue;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
case "End": {
|
|
67
|
-
nextValue = enabled[enabled.length - 1]?.value;
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
default:
|
|
71
|
-
return;
|
|
60
|
+
if (isForward) {
|
|
61
|
+
const nextIndex = (currentIndex + 1) % enabled.length;
|
|
62
|
+
nextValue = enabled[nextIndex]?.value;
|
|
63
|
+
} else if (isBackward) {
|
|
64
|
+
const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;
|
|
65
|
+
nextValue = enabled[prevIndex]?.value;
|
|
66
|
+
} else if (event.key === "Home") {
|
|
67
|
+
nextValue = enabled[0]?.value;
|
|
68
|
+
} else if (event.key === "End") {
|
|
69
|
+
nextValue = enabled[enabled.length - 1]?.value;
|
|
70
|
+
} else if (activation === "manual" && (event.key === "Enter" || event.key === " ")) {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
setActiveValue(currentValue, "keyboard");
|
|
73
|
+
return;
|
|
74
|
+
} else {
|
|
75
|
+
return;
|
|
72
76
|
}
|
|
73
77
|
if (nextValue === void 0 || nextValue === currentValue) return;
|
|
74
78
|
event.preventDefault();
|
|
75
|
-
setActiveValue(nextValue);
|
|
76
79
|
focusTab(nextValue);
|
|
80
|
+
if (activation === "automatic") {
|
|
81
|
+
setActiveValue(nextValue, "keyboard");
|
|
82
|
+
}
|
|
77
83
|
},
|
|
78
|
-
[tabs, setActiveValue, focusTab]
|
|
84
|
+
[tabs, orientation, activation, setActiveValue, focusTab]
|
|
79
85
|
);
|
|
80
86
|
const getTabProps = react.useCallback(
|
|
81
87
|
(value, options) => {
|
|
@@ -87,6 +93,7 @@ function useTabs(opts = {}) {
|
|
|
87
93
|
"aria-selected": isSelected,
|
|
88
94
|
"aria-controls": panelId(listId, value),
|
|
89
95
|
"aria-disabled": isDisabled || void 0,
|
|
96
|
+
"data-state": isSelected ? "active" : "inactive",
|
|
90
97
|
tabIndex: isSelected ? 0 : -1,
|
|
91
98
|
disabled: isDisabled,
|
|
92
99
|
ref: (node) => {
|
|
@@ -97,7 +104,7 @@ function useTabs(opts = {}) {
|
|
|
97
104
|
}
|
|
98
105
|
},
|
|
99
106
|
onClick: () => {
|
|
100
|
-
if (!isDisabled) setActiveValue(value);
|
|
107
|
+
if (!isDisabled) setActiveValue(value, "click");
|
|
101
108
|
},
|
|
102
109
|
onKeyDown: handleKeyDown(value)
|
|
103
110
|
};
|
|
@@ -106,11 +113,13 @@ function useTabs(opts = {}) {
|
|
|
106
113
|
);
|
|
107
114
|
const getPanelProps = react.useCallback(
|
|
108
115
|
(value) => {
|
|
116
|
+
const isActive = activeValue === value;
|
|
109
117
|
return {
|
|
110
118
|
id: panelId(listId, value),
|
|
111
119
|
role: "tabpanel",
|
|
112
120
|
"aria-labelledby": tabId(listId, value),
|
|
113
|
-
|
|
121
|
+
"data-state": isActive ? "active" : "inactive",
|
|
122
|
+
hidden: !isActive,
|
|
114
123
|
tabIndex: 0
|
|
115
124
|
};
|
|
116
125
|
},
|
|
@@ -122,7 +131,7 @@ function useTabs(opts = {}) {
|
|
|
122
131
|
);
|
|
123
132
|
}
|
|
124
133
|
var useIsoLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
125
|
-
function useIndicator(tabListRef, activeValue) {
|
|
134
|
+
function useIndicator(tabListRef, activeValue, orientation) {
|
|
126
135
|
const [style, setStyle] = react.useState({});
|
|
127
136
|
useIsoLayoutEffect(() => {
|
|
128
137
|
if (!tabListRef.current || activeValue === void 0) return;
|
|
@@ -135,20 +144,30 @@ function useIndicator(tabListRef, activeValue) {
|
|
|
135
144
|
if (!activeTab) return;
|
|
136
145
|
const listRect = list.getBoundingClientRect();
|
|
137
146
|
const tabRect = activeTab.getBoundingClientRect();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
if (orientation === "vertical") {
|
|
148
|
+
const y = tabRect.top - listRect.top;
|
|
149
|
+
const h = tabRect.height;
|
|
150
|
+
setStyle({
|
|
151
|
+
["--rtab-indicator-y"]: `${y}px`,
|
|
152
|
+
["--rtab-indicator-height"]: `${h}px`,
|
|
153
|
+
["--rtab-indicator-ready"]: "1"
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
const x = tabRect.left - listRect.left;
|
|
157
|
+
const w = tabRect.width;
|
|
158
|
+
setStyle({
|
|
159
|
+
["--rtab-indicator-x"]: `${x}px`,
|
|
160
|
+
["--rtab-indicator-width"]: `${w}px`,
|
|
161
|
+
["--rtab-indicator-ready"]: "1"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
145
164
|
};
|
|
146
165
|
measure();
|
|
147
166
|
if (typeof ResizeObserver === "undefined") return;
|
|
148
167
|
const ro = new ResizeObserver(measure);
|
|
149
168
|
ro.observe(tabListRef.current);
|
|
150
169
|
return () => ro.disconnect();
|
|
151
|
-
}, [activeValue, tabListRef]);
|
|
170
|
+
}, [activeValue, tabListRef, orientation]);
|
|
152
171
|
return style;
|
|
153
172
|
}
|
|
154
173
|
var TabsStyled = react.forwardRef(
|
|
@@ -160,6 +179,12 @@ var TabsStyled = react.forwardRef(
|
|
|
160
179
|
defaultValue,
|
|
161
180
|
value,
|
|
162
181
|
onChange,
|
|
182
|
+
activation = "automatic",
|
|
183
|
+
orientation = "horizontal",
|
|
184
|
+
lazyMount = false,
|
|
185
|
+
forceMount = false,
|
|
186
|
+
renderTab,
|
|
187
|
+
renderPanel,
|
|
163
188
|
className
|
|
164
189
|
}, ref) {
|
|
165
190
|
const tabDefs = tabs.map((t) => ({ value: t.value, disabled: t.disabled }));
|
|
@@ -168,46 +193,68 @@ var TabsStyled = react.forwardRef(
|
|
|
168
193
|
tabs: tabDefs,
|
|
169
194
|
defaultValue: value === void 0 ? resolvedDefault : void 0,
|
|
170
195
|
value,
|
|
171
|
-
onChange
|
|
196
|
+
onChange,
|
|
197
|
+
activation,
|
|
198
|
+
orientation
|
|
172
199
|
});
|
|
173
200
|
const tabListRef = react.useRef(null);
|
|
174
|
-
const indicatorStyle = useIndicator(tabListRef, activeValue);
|
|
201
|
+
const indicatorStyle = useIndicator(tabListRef, activeValue, orientation);
|
|
202
|
+
const activatedRef = react.useRef(/* @__PURE__ */ new Set());
|
|
203
|
+
if (activeValue !== void 0) activatedRef.current.add(activeValue);
|
|
175
204
|
const rootClass = ["rtab-root", className].filter(Boolean).join(" ");
|
|
176
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"
|
|
205
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
ref,
|
|
209
|
+
className: rootClass,
|
|
210
|
+
"data-orientation": orientation,
|
|
211
|
+
children: [
|
|
212
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
ref: tabListRef,
|
|
216
|
+
role: "tablist",
|
|
217
|
+
"aria-orientation": orientation,
|
|
218
|
+
className: "rtab-list",
|
|
219
|
+
"data-variant": variant,
|
|
220
|
+
"data-size": size,
|
|
221
|
+
"data-tone": tone,
|
|
222
|
+
"data-orientation": orientation,
|
|
223
|
+
style: indicatorStyle,
|
|
224
|
+
children: [
|
|
225
|
+
tabs.map((tab, index) => {
|
|
226
|
+
const isActive = activeValue === tab.value;
|
|
227
|
+
const isDisabled = !!tab.disabled;
|
|
228
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
229
|
+
"button",
|
|
230
|
+
{
|
|
231
|
+
className: "rtab-tab",
|
|
232
|
+
...getTabProps(tab.value, { disabled: tab.disabled }),
|
|
233
|
+
children: renderTab ? renderTab({ tab, index, isActive, isDisabled }) : tab.label
|
|
234
|
+
},
|
|
235
|
+
tab.value
|
|
236
|
+
);
|
|
237
|
+
}),
|
|
238
|
+
(variant === "line" || variant === "solid" || variant === "pill") && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rtab-indicator", "aria-hidden": "true" })
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rtab-panels", children: tabs.map((tab) => {
|
|
243
|
+
const isActive = activeValue === tab.value;
|
|
244
|
+
const shouldRender = forceMount || !lazyMount || activatedRef.current.has(tab.value);
|
|
245
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
246
|
+
"div",
|
|
190
247
|
{
|
|
191
|
-
className: "rtab-
|
|
192
|
-
...
|
|
193
|
-
children: tab.
|
|
248
|
+
className: "rtab-panel",
|
|
249
|
+
...getPanelProps(tab.value),
|
|
250
|
+
children: shouldRender ? renderPanel ? renderPanel({ tab, isActive }) : tab.content : null
|
|
194
251
|
},
|
|
195
252
|
tab.value
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rtab-panels", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
202
|
-
"div",
|
|
203
|
-
{
|
|
204
|
-
className: "rtab-panel",
|
|
205
|
-
...getPanelProps(tab.value),
|
|
206
|
-
children: tab.content
|
|
207
|
-
},
|
|
208
|
-
tab.value
|
|
209
|
-
)) })
|
|
210
|
-
] });
|
|
253
|
+
);
|
|
254
|
+
}) })
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
);
|
|
211
258
|
}
|
|
212
259
|
);
|
|
213
260
|
|
package/dist/styled.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useTabs.ts","../src/styled/TabsStyled.tsx"],"names":["useRef","useState","useCallback","useMemo","useLayoutEffect","useEffect","forwardRef","TabsStyled","jsxs","jsx"],"mappings":"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","file":"styled.cjs","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","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/useTabs.ts","../src/styled/TabsStyled.tsx"],"names":["useRef","useState","useCallback","useMemo","useLayoutEffect","useEffect","forwardRef","TabsStyled","jsxs","jsx"],"mappings":"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","file":"styled.cjs","sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n useCallback,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nexport type TabsActivation = \"automatic\" | \"manual\";\nexport type TabsOrientation = \"horizontal\" | \"vertical\";\nexport type TabsChangeReason = \"click\" | \"keyboard\" | \"programmatic\";\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. Optional second arg reports the trigger reason. */\n onChange?: (value: string, reason: TabsChangeReason) => void;\n /** \"automatic\" (default) — arrow keys move focus AND activate. \"manual\" — arrows move focus only; Enter/Space activates. */\n activation?: TabsActivation;\n /** Affects keyboard nav. Default \"horizontal\". */\n orientation?: TabsOrientation;\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 \"data-state\": \"active\" | \"inactive\";\n tabIndex: number;\n}\n\nexport interface PanelProps extends HTMLAttributes<HTMLDivElement> {\n id: string;\n role: \"tabpanel\";\n \"aria-labelledby\": string;\n \"data-state\": \"active\" | \"inactive\";\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 {\n tabs = [],\n value: controlledValue,\n defaultValue,\n onChange,\n activation = \"automatic\",\n orientation = \"horizontal\",\n } = 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 const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const setActiveValue = useCallback(\n (next: string, reason: TabsChangeReason = \"programmatic\") => {\n const tab = tabs.find((t) => t.value === next);\n if (tab?.disabled) return;\n if (isControlled) {\n if (next !== controlledValue) onChangeRef.current?.(next, reason);\n } else {\n if (next === internalValue) return;\n setInternalValue(next);\n onChangeRef.current?.(next, reason);\n }\n },\n [tabs, isControlled, controlledValue, internalValue],\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 const isForward =\n orientation === \"vertical\"\n ? event.key === \"ArrowDown\"\n : event.key === \"ArrowRight\";\n const isBackward =\n orientation === \"vertical\"\n ? event.key === \"ArrowUp\"\n : event.key === \"ArrowLeft\";\n\n let nextValue: string | undefined;\n\n if (isForward) {\n const nextIndex = (currentIndex + 1) % enabled.length;\n nextValue = enabled[nextIndex]?.value;\n } else if (isBackward) {\n const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;\n nextValue = enabled[prevIndex]?.value;\n } else if (event.key === \"Home\") {\n nextValue = enabled[0]?.value;\n } else if (event.key === \"End\") {\n nextValue = enabled[enabled.length - 1]?.value;\n } else if (\n activation === \"manual\" &&\n (event.key === \"Enter\" || event.key === \" \")\n ) {\n event.preventDefault();\n setActiveValue(currentValue, \"keyboard\");\n return;\n } else {\n return;\n }\n\n if (nextValue === undefined || nextValue === currentValue) return;\n event.preventDefault();\n focusTab(nextValue);\n if (activation === \"automatic\") {\n setActiveValue(nextValue, \"keyboard\");\n }\n },\n [tabs, orientation, activation, 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 \"data-state\": isSelected ? \"active\" : \"inactive\",\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, \"click\");\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 const isActive = activeValue === value;\n return {\n id: panelId(listId, value),\n role: \"tabpanel\",\n \"aria-labelledby\": tabId(listId, value),\n \"data-state\": isActive ? \"active\" : \"inactive\",\n hidden: !isActive,\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","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}\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 /** 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 renderTab,\n renderPanel,\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 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 return (\n <div\n ref={ref}\n className={rootClass}\n data-orientation={orientation}\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 style={indicatorStyle}\n >\n {tabs.map((tab, index) => {\n const isActive = activeValue === tab.value;\n const isDisabled = !!tab.disabled;\n return (\n <button\n key={tab.value}\n className=\"rtab-tab\"\n {...getTabProps(tab.value, { disabled: tab.disabled })}\n >\n {renderTab\n ? renderTab({ tab, index, isActive, isDisabled })\n : tab.label}\n </button>\n );\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 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/styled.d.cts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { TabsChangeReason, TabsActivation, TabsOrientation } from './index.cjs';
|
|
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;
|
|
12
13
|
}
|
|
14
|
+
interface TabsRenderTabContext {
|
|
15
|
+
tab: TabItem;
|
|
16
|
+
index: number;
|
|
17
|
+
isActive: boolean;
|
|
18
|
+
isDisabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface TabsRenderPanelContext {
|
|
21
|
+
tab: TabItem;
|
|
22
|
+
isActive: boolean;
|
|
23
|
+
}
|
|
13
24
|
interface TabsStyledProps {
|
|
14
25
|
tabs: TabItem[];
|
|
15
26
|
variant?: TabsVariant;
|
|
@@ -17,7 +28,20 @@ interface TabsStyledProps {
|
|
|
17
28
|
tone?: TabsTone;
|
|
18
29
|
defaultValue?: string;
|
|
19
30
|
value?: string;
|
|
20
|
-
|
|
31
|
+
/** Optional second arg reports the trigger reason ("click" | "keyboard" | "programmatic"). */
|
|
32
|
+
onChange?: (value: string, reason: TabsChangeReason) => void;
|
|
33
|
+
/** "automatic" (default) — arrow keys move focus AND activate. "manual" — arrows move focus only. */
|
|
34
|
+
activation?: TabsActivation;
|
|
35
|
+
/** Affects keyboard nav direction. Default "horizontal". */
|
|
36
|
+
orientation?: TabsOrientation;
|
|
37
|
+
/** Only mount panels after they've been activated at least once. Default: false (all panels mount eagerly). */
|
|
38
|
+
lazyMount?: boolean;
|
|
39
|
+
/** Keep all panels mounted regardless of activation (useful with `lazyMount` overrides). Default: false. */
|
|
40
|
+
forceMount?: boolean;
|
|
41
|
+
/** Replace the default tab button content. The button shell (a11y, ref, key) stays owned by the component. */
|
|
42
|
+
renderTab?: (ctx: TabsRenderTabContext) => ReactNode;
|
|
43
|
+
/** Replace the default panel rendering. */
|
|
44
|
+
renderPanel?: (ctx: TabsRenderPanelContext) => ReactNode;
|
|
21
45
|
className?: string;
|
|
22
46
|
}
|
|
23
47
|
declare const TabsStyled: react.ForwardRefExoticComponent<TabsStyledProps & react.RefAttributes<HTMLDivElement>>;
|
package/dist/styled.d.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
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;
|
|
12
13
|
}
|
|
14
|
+
interface TabsRenderTabContext {
|
|
15
|
+
tab: TabItem;
|
|
16
|
+
index: number;
|
|
17
|
+
isActive: boolean;
|
|
18
|
+
isDisabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface TabsRenderPanelContext {
|
|
21
|
+
tab: TabItem;
|
|
22
|
+
isActive: boolean;
|
|
23
|
+
}
|
|
13
24
|
interface TabsStyledProps {
|
|
14
25
|
tabs: TabItem[];
|
|
15
26
|
variant?: TabsVariant;
|
|
@@ -17,7 +28,20 @@ interface TabsStyledProps {
|
|
|
17
28
|
tone?: TabsTone;
|
|
18
29
|
defaultValue?: string;
|
|
19
30
|
value?: string;
|
|
20
|
-
|
|
31
|
+
/** Optional second arg reports the trigger reason ("click" | "keyboard" | "programmatic"). */
|
|
32
|
+
onChange?: (value: string, reason: TabsChangeReason) => void;
|
|
33
|
+
/** "automatic" (default) — arrow keys move focus AND activate. "manual" — arrows move focus only. */
|
|
34
|
+
activation?: TabsActivation;
|
|
35
|
+
/** Affects keyboard nav direction. Default "horizontal". */
|
|
36
|
+
orientation?: TabsOrientation;
|
|
37
|
+
/** Only mount panels after they've been activated at least once. Default: false (all panels mount eagerly). */
|
|
38
|
+
lazyMount?: boolean;
|
|
39
|
+
/** Keep all panels mounted regardless of activation (useful with `lazyMount` overrides). Default: false. */
|
|
40
|
+
forceMount?: boolean;
|
|
41
|
+
/** Replace the default tab button content. The button shell (a11y, ref, key) stays owned by the component. */
|
|
42
|
+
renderTab?: (ctx: TabsRenderTabContext) => ReactNode;
|
|
43
|
+
/** Replace the default panel rendering. */
|
|
44
|
+
renderPanel?: (ctx: TabsRenderPanelContext) => ReactNode;
|
|
21
45
|
className?: string;
|
|
22
46
|
}
|
|
23
47
|
declare const TabsStyled: react.ForwardRefExoticComponent<TabsStyledProps & react.RefAttributes<HTMLDivElement>>;
|
package/dist/styled.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useTabs } from './chunk-
|
|
1
|
+
import { useTabs } from './chunk-NNDW3W6L.js';
|
|
2
2
|
import { forwardRef, useRef, useState, useLayoutEffect, useEffect } 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,6 +51,12 @@ 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
|
+
renderTab,
|
|
59
|
+
renderPanel,
|
|
44
60
|
className
|
|
45
61
|
}, ref) {
|
|
46
62
|
const tabDefs = tabs.map((t) => ({ value: t.value, disabled: t.disabled }));
|
|
@@ -49,46 +65,68 @@ var TabsStyled = forwardRef(
|
|
|
49
65
|
tabs: tabDefs,
|
|
50
66
|
defaultValue: value === void 0 ? resolvedDefault : void 0,
|
|
51
67
|
value,
|
|
52
|
-
onChange
|
|
68
|
+
onChange,
|
|
69
|
+
activation,
|
|
70
|
+
orientation
|
|
53
71
|
});
|
|
54
72
|
const tabListRef = useRef(null);
|
|
55
|
-
const indicatorStyle = useIndicator(tabListRef, activeValue);
|
|
73
|
+
const indicatorStyle = useIndicator(tabListRef, activeValue, orientation);
|
|
74
|
+
const activatedRef = useRef(/* @__PURE__ */ new Set());
|
|
75
|
+
if (activeValue !== void 0) activatedRef.current.add(activeValue);
|
|
56
76
|
const rootClass = ["rtab-root", className].filter(Boolean).join(" ");
|
|
57
|
-
return /* @__PURE__ */ jsxs(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
77
|
+
return /* @__PURE__ */ jsxs(
|
|
78
|
+
"div",
|
|
79
|
+
{
|
|
80
|
+
ref,
|
|
81
|
+
className: rootClass,
|
|
82
|
+
"data-orientation": orientation,
|
|
83
|
+
children: [
|
|
84
|
+
/* @__PURE__ */ jsxs(
|
|
85
|
+
"div",
|
|
86
|
+
{
|
|
87
|
+
ref: tabListRef,
|
|
88
|
+
role: "tablist",
|
|
89
|
+
"aria-orientation": orientation,
|
|
90
|
+
className: "rtab-list",
|
|
91
|
+
"data-variant": variant,
|
|
92
|
+
"data-size": size,
|
|
93
|
+
"data-tone": tone,
|
|
94
|
+
"data-orientation": orientation,
|
|
95
|
+
style: indicatorStyle,
|
|
96
|
+
children: [
|
|
97
|
+
tabs.map((tab, index) => {
|
|
98
|
+
const isActive = activeValue === tab.value;
|
|
99
|
+
const isDisabled = !!tab.disabled;
|
|
100
|
+
return /* @__PURE__ */ jsx(
|
|
101
|
+
"button",
|
|
102
|
+
{
|
|
103
|
+
className: "rtab-tab",
|
|
104
|
+
...getTabProps(tab.value, { disabled: tab.disabled }),
|
|
105
|
+
children: renderTab ? renderTab({ tab, index, isActive, isDisabled }) : tab.label
|
|
106
|
+
},
|
|
107
|
+
tab.value
|
|
108
|
+
);
|
|
109
|
+
}),
|
|
110
|
+
(variant === "line" || variant === "solid" || variant === "pill") && /* @__PURE__ */ jsx("span", { className: "rtab-indicator", "aria-hidden": "true" })
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
/* @__PURE__ */ jsx("div", { className: "rtab-panels", children: tabs.map((tab) => {
|
|
115
|
+
const isActive = activeValue === tab.value;
|
|
116
|
+
const shouldRender = forceMount || !lazyMount || activatedRef.current.has(tab.value);
|
|
117
|
+
return /* @__PURE__ */ jsx(
|
|
118
|
+
"div",
|
|
71
119
|
{
|
|
72
|
-
className: "rtab-
|
|
73
|
-
...
|
|
74
|
-
children: tab.
|
|
120
|
+
className: "rtab-panel",
|
|
121
|
+
...getPanelProps(tab.value),
|
|
122
|
+
children: shouldRender ? renderPanel ? renderPanel({ tab, isActive }) : tab.content : null
|
|
75
123
|
},
|
|
76
124
|
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
|
-
] });
|
|
125
|
+
);
|
|
126
|
+
}) })
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
);
|
|
92
130
|
}
|
|
93
131
|
);
|
|
94
132
|
|
package/dist/styled.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 { 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}\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 /** 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 renderTab,\n renderPanel,\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 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 return (\n <div\n ref={ref}\n className={rootClass}\n data-orientation={orientation}\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 style={indicatorStyle}\n >\n {tabs.map((tab, index) => {\n const isActive = activeValue === tab.value;\n const isDisabled = !!tab.disabled;\n return (\n <button\n key={tab.value}\n className=\"rtab-tab\"\n {...getTabProps(tab.value, { disabled: tab.disabled })}\n >\n {renderTab\n ? renderTab({ tab, index, isActive, isDisabled })\n : tab.label}\n </button>\n );\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 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,35 @@
|
|
|
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; }
|
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"]}
|