@mshafiqyajid/react-tabs 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-UCRNSS7N.js → chunk-NNDW3W6L.js} +43 -34
- package/dist/chunk-NNDW3W6L.js.map +1 -0
- package/dist/index.cjs +41 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +1 -1
- package/dist/styled.cjs +260 -74
- package/dist/styled.cjs.map +1 -1
- package/dist/styled.d.cts +37 -2
- package/dist/styled.d.ts +37 -2
- package/dist/styled.js +221 -44
- package/dist/styled.js.map +1 -1
- package/dist/styles.css +120 -0
- package/package.json +1 -1
- package/dist/chunk-UCRNSS7N.js.map +0 -1
|
@@ -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":"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","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