@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.
@@ -12,7 +12,14 @@ function useStableId() {
12
12
  return ref.current;
13
13
  }
14
14
  function useTabs(opts = {}) {
15
- const { tabs = [], value: controlledValue, defaultValue, onChange } = opts;
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) onChange?.(next);
37
+ if (next !== controlledValue) onChangeRef.current?.(next, reason);
29
38
  } else {
30
39
  if (next === internalValue) return;
31
40
  setInternalValue(next);
32
- onChange?.(next);
41
+ onChangeRef.current?.(next, reason);
33
42
  }
34
43
  },
35
- [tabs, isControlled, controlledValue, internalValue, onChange]
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
- switch (event.key) {
47
- case "ArrowRight":
48
- case "ArrowDown": {
49
- const nextIndex = (currentIndex + 1) % enabled.length;
50
- nextValue = enabled[nextIndex]?.value;
51
- break;
52
- }
53
- case "ArrowLeft":
54
- case "ArrowUp": {
55
- const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;
56
- nextValue = enabled[prevIndex]?.value;
57
- break;
58
- }
59
- case "Home": {
60
- nextValue = enabled[0]?.value;
61
- break;
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
- hidden: activeValue !== value,
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-UCRNSS7N.js.map
124
- //# sourceMappingURL=chunk-UCRNSS7N.js.map
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 { tabs = [], value: controlledValue, defaultValue, onChange } = opts;
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) onChange?.(next);
39
+ if (next !== controlledValue) onChangeRef.current?.(next, reason);
31
40
  } else {
32
41
  if (next === internalValue) return;
33
42
  setInternalValue(next);
34
- onChange?.(next);
43
+ onChangeRef.current?.(next, reason);
35
44
  }
36
45
  },
37
- [tabs, isControlled, controlledValue, internalValue, onChange]
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
- switch (event.key) {
49
- case "ArrowRight":
50
- case "ArrowDown": {
51
- const nextIndex = (currentIndex + 1) % enabled.length;
52
- nextValue = enabled[nextIndex]?.value;
53
- break;
54
- }
55
- case "ArrowLeft":
56
- case "ArrowUp": {
57
- const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;
58
- nextValue = enabled[prevIndex]?.value;
59
- break;
60
- }
61
- case "Home": {
62
- nextValue = enabled[0]?.value;
63
- break;
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
- hidden: activeValue !== value,
120
+ "data-state": isActive ? "active" : "inactive",
121
+ hidden: !isActive,
113
122
  tabIndex: 0
114
123
  };
115
124
  },
@@ -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
@@ -1,3 +1,3 @@
1
- export { useTabs } from './chunk-UCRNSS7N.js';
1
+ export { useTabs } from './chunk-NNDW3W6L.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
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 { tabs = [], value: controlledValue, defaultValue, onChange } = opts;
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) onChange?.(next);
40
+ if (next !== controlledValue) onChangeRef.current?.(next, reason);
32
41
  } else {
33
42
  if (next === internalValue) return;
34
43
  setInternalValue(next);
35
- onChange?.(next);
44
+ onChangeRef.current?.(next, reason);
36
45
  }
37
46
  },
38
- [tabs, isControlled, controlledValue, internalValue, onChange]
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
- switch (event.key) {
50
- case "ArrowRight":
51
- case "ArrowDown": {
52
- const nextIndex = (currentIndex + 1) % enabled.length;
53
- nextValue = enabled[nextIndex]?.value;
54
- break;
55
- }
56
- case "ArrowLeft":
57
- case "ArrowUp": {
58
- const prevIndex = (currentIndex - 1 + enabled.length) % enabled.length;
59
- nextValue = enabled[prevIndex]?.value;
60
- break;
61
- }
62
- case "Home": {
63
- nextValue = enabled[0]?.value;
64
- break;
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
- hidden: activeValue !== value,
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
- const x = tabRect.left - listRect.left;
139
- const w = tabRect.width;
140
- setStyle({
141
- ["--rtab-indicator-x"]: `${x}px`,
142
- ["--rtab-indicator-width"]: `${w}px`,
143
- ["--rtab-indicator-ready"]: "1"
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("div", { ref, className: rootClass, children: [
177
- /* @__PURE__ */ jsxRuntime.jsxs(
178
- "div",
179
- {
180
- ref: tabListRef,
181
- role: "tablist",
182
- className: "rtab-list",
183
- "data-variant": variant,
184
- "data-size": size,
185
- "data-tone": tone,
186
- style: indicatorStyle,
187
- children: [
188
- tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
189
- "button",
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-tab",
192
- ...getTabProps(tab.value, { disabled: tab.disabled }),
193
- children: tab.label
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
- (variant === "line" || variant === "solid" || variant === "pill") && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rtab-indicator", "aria-hidden": "true" })
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
 
@@ -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
- onChange?: (value: string) => void;
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
- onChange?: (value: string) => void;
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-UCRNSS7N.js';
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
- const x = tabRect.left - listRect.left;
20
- const w = tabRect.width;
21
- setStyle({
22
- ["--rtab-indicator-x"]: `${x}px`,
23
- ["--rtab-indicator-width"]: `${w}px`,
24
- ["--rtab-indicator-ready"]: "1"
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("div", { ref, className: rootClass, children: [
58
- /* @__PURE__ */ jsxs(
59
- "div",
60
- {
61
- ref: tabListRef,
62
- role: "tablist",
63
- className: "rtab-list",
64
- "data-variant": variant,
65
- "data-size": size,
66
- "data-tone": tone,
67
- style: indicatorStyle,
68
- children: [
69
- tabs.map((tab) => /* @__PURE__ */ jsx(
70
- "button",
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-tab",
73
- ...getTabProps(tab.value, { disabled: tab.disabled }),
74
- children: tab.label
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
- (variant === "line" || variant === "solid" || variant === "pill") && /* @__PURE__ */ jsx("span", { className: "rtab-indicator", "aria-hidden": "true" })
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
 
@@ -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":";;;;AAgEA,IAAM,kBAAA,GACJ,OAAO,MAAA,KAAW,WAAA,GAAc,eAAA,GAAkB,SAAA;AAEpD,SAAS,YAAA,CACP,UAAA,EACA,WAAA,EACA,WAAA,EACA;AACA,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAAwB,EAAE,CAAA;AAEpD,EAAA,kBAAA,CAAmB,MAAM;AACvB,IAAA,IAAI,CAAC,UAAA,CAAW,OAAA,IAAW,WAAA,KAAgB,MAAA,EAAW;AAEtD,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,MAAM,OAAO,UAAA,CAAW,OAAA;AACxB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,MAAM,YAAY,IAAA,CAAK,aAAA;AAAA,QACrB,CAAA,sBAAA;AAAA,OACF;AACA,MAAA,IAAI,CAAC,SAAA,EAAW;AAEhB,MAAA,MAAM,QAAA,GAAW,KAAK,qBAAA,EAAsB;AAC5C,MAAA,MAAM,OAAA,GAAU,UAAU,qBAAA,EAAsB;AAEhD,MAAA,IAAI,gBAAgB,UAAA,EAAY;AAC9B,QAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,GAAA,GAAM,QAAA,CAAS,GAAA;AACjC,QAAA,MAAM,IAAI,OAAA,CAAQ,MAAA;AAClB,QAAA,QAAA,CAAS;AAAA,UACP,CAAC,oBAA8B,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,UACtC,CAAC,yBAAmC,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,UAC3C,CAAC,wBAAkC,GAAG;AAAA,SACvC,CAAA;AAAA,MACH,CAAA,MAAO;AACL,QAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,IAAA,GAAO,QAAA,CAAS,IAAA;AAClC,QAAA,MAAM,IAAI,OAAA,CAAQ,KAAA;AAClB,QAAA,QAAA,CAAS;AAAA,UACP,CAAC,oBAA8B,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,UACtC,CAAC,wBAAkC,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,UAC1C,CAAC,wBAAkC,GAAG;AAAA,SACvC,CAAA;AAAA,MACH;AAAA,IACF,CAAA;AAEA,IAAA,OAAA,EAAQ;AAER,IAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAC3C,IAAA,MAAM,EAAA,GAAK,IAAI,cAAA,CAAe,OAAO,CAAA;AACrC,IAAA,EAAA,CAAG,OAAA,CAAQ,WAAW,OAAO,CAAA;AAC7B,IAAA,OAAO,MAAM,GAAG,UAAA,EAAW;AAAA,EAC7B,CAAA,EAAG,CAAC,WAAA,EAAa,UAAA,EAAY,WAAW,CAAC,CAAA;AAEzC,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,UAAA,GAAa,UAAA;AAAA,EACxB,SAASA,WAAAA,CACP;AAAA,IACE,IAAA;AAAA,IACA,OAAA,GAAU,MAAA;AAAA,IACV,IAAA,GAAO,IAAA;AAAA,IACP,IAAA,GAAO,SAAA;AAAA,IACP,YAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA,GAAa,WAAA;AAAA,IACb,WAAA,GAAc,YAAA;AAAA,IACd,SAAA,GAAY,KAAA;AAAA,IACZ,UAAA,GAAa,KAAA;AAAA,IACb,SAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAO,QAAA,EAAU,CAAA,CAAE,QAAA,EAAS,CAAE,CAAA;AAE1E,IAAA,MAAM,eAAA,GACJ,gBAAgB,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,QAAQ,CAAA,EAAG,KAAA;AAEjD,IAAA,MAAM,EAAE,WAAA,EAAa,WAAA,EAAa,aAAA,KAAkB,OAAA,CAAQ;AAAA,MAC1D,IAAA,EAAM,OAAA;AAAA,MACN,YAAA,EAAc,KAAA,KAAU,MAAA,GAAY,eAAA,GAAkB,MAAA;AAAA,MACtD,KAAA;AAAA,MACA,QAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAA,GAAa,OAAuB,IAAI,CAAA;AAC9C,IAAA,MAAM,cAAA,GAAiB,YAAA,CAAa,UAAA,EAAY,WAAA,EAAa,WAAW,CAAA;AAGxE,IAAA,MAAM,YAAA,GAAe,MAAA,iBAAoB,IAAI,GAAA,EAAK,CAAA;AAClD,IAAA,IAAI,WAAA,KAAgB,MAAA,EAAW,YAAA,CAAa,OAAA,CAAQ,IAAI,WAAW,CAAA;AAEnE,IAAA,MAAM,SAAA,GAAY,CAAC,WAAA,EAAa,SAAS,EAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAEnE,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,SAAA;AAAA,QACX,kBAAA,EAAkB,WAAA;AAAA,QAElB,QAAA,EAAA;AAAA,0BAAA,IAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,UAAA;AAAA,cACL,IAAA,EAAK,SAAA;AAAA,cACL,kBAAA,EAAkB,WAAA;AAAA,cAClB,SAAA,EAAU,WAAA;AAAA,cACV,cAAA,EAAc,OAAA;AAAA,cACd,WAAA,EAAW,IAAA;AAAA,cACX,WAAA,EAAW,IAAA;AAAA,cACX,kBAAA,EAAkB,WAAA;AAAA,cAClB,KAAA,EAAO,cAAA;AAAA,cAEN,QAAA,EAAA;AAAA,gBAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,EAAK,KAAA,KAAU;AACxB,kBAAA,MAAM,QAAA,GAAW,gBAAgB,GAAA,CAAI,KAAA;AACrC,kBAAA,MAAM,UAAA,GAAa,CAAC,CAAC,GAAA,CAAI,QAAA;AACzB,kBAAA,uBACE,GAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBAEC,SAAA,EAAU,UAAA;AAAA,sBACT,GAAG,YAAY,GAAA,CAAI,KAAA,EAAO,EAAE,QAAA,EAAU,GAAA,CAAI,UAAU,CAAA;AAAA,sBAEpD,QAAA,EAAA,SAAA,GACG,UAAU,EAAE,GAAA,EAAK,OAAO,QAAA,EAAU,UAAA,EAAY,CAAA,GAC9C,GAAA,CAAI;AAAA,qBAAA;AAAA,oBANH,GAAA,CAAI;AAAA,mBAOX;AAAA,gBAEJ,CAAC,CAAA;AAAA,gBAAA,CACC,OAAA,KAAY,MAAA,IAAU,OAAA,KAAY,OAAA,IAAW,OAAA,KAAY,MAAA,qBACzD,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EAAiB,aAAA,EAAY,MAAA,EAAO;AAAA;AAAA;AAAA,WAExD;AAAA,8BACC,KAAA,EAAA,EAAI,SAAA,EAAU,eACZ,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ;AACjB,YAAA,MAAM,QAAA,GAAW,gBAAgB,GAAA,CAAI,KAAA;AACrC,YAAA,MAAM,YAAA,GACJ,cAAc,CAAC,SAAA,IAAa,aAAa,OAAA,CAAQ,GAAA,CAAI,IAAI,KAAK,CAAA;AAChE,YAAA,uBACE,GAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBAEC,SAAA,EAAU,YAAA;AAAA,gBACT,GAAG,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA;AAAA,gBAE1B,QAAA,EAAA,YAAA,GACG,cACE,WAAA,CAAY,EAAE,KAAK,QAAA,EAAU,CAAA,GAC7B,GAAA,CAAI,OAAA,GACN;AAAA,eAAA;AAAA,cARC,GAAA,CAAI;AAAA,aASX;AAAA,UAEJ,CAAC,CAAA,EACH;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF","file":"styled.js","sourcesContent":["import {\n type CSSProperties,\n type ReactNode,\n forwardRef,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport {\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,6 +1,6 @@
1
1
  {
2
2
  "name": "@mshafiqyajid/react-tabs",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Headless tabs hook and styled component for React. Accessible, keyboard-friendly, animated, SSR-safe, fully typed.",
5
5
  "keywords": [
6
6
  "react",
@@ -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"]}