@mshafiqyajid/react-tabs 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/chunk-UCRNSS7N.js +124 -0
- package/dist/chunk-UCRNSS7N.js.map +1 -0
- package/dist/index.cjs +126 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/styled.cjs +216 -0
- package/dist/styled.cjs.map +1 -0
- package/dist/styled.d.cts +25 -0
- package/dist/styled.d.ts +25 -0
- package/dist/styled.js +97 -0
- package/dist/styled.js.map +1 -0
- package/dist/styles.css +298 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shafiq Yajid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @mshafiqyajid/react-tabs
|
|
2
|
+
|
|
3
|
+
Headless tabs hook and styled component for React. Accessible, keyboard-friendly, animated, SSR-safe, fully typed.
|
|
4
|
+
|
|
5
|
+
**[Full docs →](https://docs.shafiqyajid.com/react/tabs/)**
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @mshafiqyajid/react-tabs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start — styled component
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { TabsStyled } from "@mshafiqyajid/react-tabs/styled";
|
|
17
|
+
import "@mshafiqyajid/react-tabs/styles.css";
|
|
18
|
+
|
|
19
|
+
const tabs = [
|
|
20
|
+
{ value: "account", label: "Account", content: <div>Account settings</div> },
|
|
21
|
+
{ value: "security", label: "Security", content: <div>Security settings</div> },
|
|
22
|
+
{ value: "billing", label: "Billing", content: <div>Billing info</div>, disabled: true },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function App() {
|
|
26
|
+
return <TabsStyled tabs={tabs} defaultValue="account" />;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start — headless hook
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { useTabs } from "@mshafiqyajid/react-tabs";
|
|
34
|
+
|
|
35
|
+
function MyTabs() {
|
|
36
|
+
const { getTabProps, getPanelProps, activeValue } = useTabs({
|
|
37
|
+
defaultValue: "account",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
<div role="tablist">
|
|
43
|
+
<button {...getTabProps("account")}>Account</button>
|
|
44
|
+
<button {...getTabProps("security")}>Security</button>
|
|
45
|
+
</div>
|
|
46
|
+
<div {...getPanelProps("account")}>Account content</div>
|
|
47
|
+
<div {...getPanelProps("security")}>Security content</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Props — `TabsStyled`
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Default | Description |
|
|
56
|
+
|------|------|---------|-------------|
|
|
57
|
+
| `tabs` | `TabItem[]` | required | Array of tab definitions |
|
|
58
|
+
| `variant` | `"line" \| "solid" \| "pill"` | `"line"` | Visual style of the tab indicator |
|
|
59
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of tabs |
|
|
60
|
+
| `tone` | `"neutral" \| "primary"` | `"neutral"` | Color tone |
|
|
61
|
+
| `defaultValue` | `string` | — | Initial active tab (uncontrolled) |
|
|
62
|
+
| `value` | `string` | — | Controlled active tab |
|
|
63
|
+
| `onChange` | `(value: string) => void` | — | Called when tab changes |
|
|
64
|
+
|
|
65
|
+
### `TabItem`
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
interface TabItem {
|
|
69
|
+
value: string;
|
|
70
|
+
label: React.ReactNode;
|
|
71
|
+
content: React.ReactNode;
|
|
72
|
+
disabled?: boolean;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Props — `useTabs`
|
|
77
|
+
|
|
78
|
+
| Option | Type | Description |
|
|
79
|
+
|--------|------|-------------|
|
|
80
|
+
| `defaultValue` | `string` | Initial active tab (uncontrolled) |
|
|
81
|
+
| `value` | `string` | Controlled active tab |
|
|
82
|
+
| `onChange` | `(value: string) => void` | Called when tab changes |
|
|
83
|
+
| `tabs` | `{ value: string; disabled?: boolean }[]` | Tab definitions for keyboard navigation |
|
|
84
|
+
|
|
85
|
+
### Returns
|
|
86
|
+
|
|
87
|
+
| Key | Type | Description |
|
|
88
|
+
|-----|------|-------------|
|
|
89
|
+
| `activeValue` | `string \| undefined` | Currently active tab value |
|
|
90
|
+
| `getTabProps` | `(value: string) => object` | Spread onto each tab button |
|
|
91
|
+
| `getPanelProps` | `(value: string) => object` | Spread onto each panel |
|
|
92
|
+
|
|
93
|
+
## Keyboard navigation
|
|
94
|
+
|
|
95
|
+
| Key | Action |
|
|
96
|
+
|-----|--------|
|
|
97
|
+
| `ArrowRight` / `ArrowDown` | Move to next tab |
|
|
98
|
+
| `ArrowLeft` / `ArrowUp` | Move to previous tab |
|
|
99
|
+
| `Home` | Move to first tab |
|
|
100
|
+
| `End` | Move to last tab |
|
|
101
|
+
|
|
102
|
+
## Dark mode
|
|
103
|
+
|
|
104
|
+
Add `data-theme="dark"` to a parent element (or `data-rtab-theme="dark"` for component-scoped dark mode). Never uses `@media (prefers-color-scheme: dark)`.
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<div data-theme="dark">
|
|
108
|
+
<!-- TabsStyled inherits dark mode here -->
|
|
109
|
+
</div>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/useTabs.ts
|
|
4
|
+
var tabId = (listId, value) => `${listId}-tab-${value}`;
|
|
5
|
+
var panelId = (listId, value) => `${listId}-panel-${value}`;
|
|
6
|
+
var counter = 0;
|
|
7
|
+
function useStableId() {
|
|
8
|
+
const ref = useRef(null);
|
|
9
|
+
if (ref.current === null) {
|
|
10
|
+
ref.current = `rtabs-${++counter}`;
|
|
11
|
+
}
|
|
12
|
+
return ref.current;
|
|
13
|
+
}
|
|
14
|
+
function useTabs(opts = {}) {
|
|
15
|
+
const { tabs = [], value: controlledValue, defaultValue, onChange } = opts;
|
|
16
|
+
const listId = useStableId();
|
|
17
|
+
const isControlled = controlledValue !== void 0;
|
|
18
|
+
const [internalValue, setInternalValue] = useState(
|
|
19
|
+
defaultValue
|
|
20
|
+
);
|
|
21
|
+
const activeValue = isControlled ? controlledValue : internalValue;
|
|
22
|
+
const tabRefs = useRef(/* @__PURE__ */ new Map());
|
|
23
|
+
const setActiveValue = useCallback(
|
|
24
|
+
(next) => {
|
|
25
|
+
const tab = tabs.find((t) => t.value === next);
|
|
26
|
+
if (tab?.disabled) return;
|
|
27
|
+
if (isControlled) {
|
|
28
|
+
if (next !== controlledValue) onChange?.(next);
|
|
29
|
+
} else {
|
|
30
|
+
if (next === internalValue) return;
|
|
31
|
+
setInternalValue(next);
|
|
32
|
+
onChange?.(next);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
[tabs, isControlled, controlledValue, internalValue, onChange]
|
|
36
|
+
);
|
|
37
|
+
const focusTab = useCallback((value) => {
|
|
38
|
+
tabRefs.current.get(value)?.focus();
|
|
39
|
+
}, []);
|
|
40
|
+
const handleKeyDown = useCallback(
|
|
41
|
+
(currentValue) => (event) => {
|
|
42
|
+
const enabled = tabs.filter((t) => !t.disabled);
|
|
43
|
+
if (enabled.length === 0) return;
|
|
44
|
+
const currentIndex = enabled.findIndex((t) => t.value === currentValue);
|
|
45
|
+
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;
|
|
69
|
+
}
|
|
70
|
+
if (nextValue === void 0 || nextValue === currentValue) return;
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
setActiveValue(nextValue);
|
|
73
|
+
focusTab(nextValue);
|
|
74
|
+
},
|
|
75
|
+
[tabs, setActiveValue, focusTab]
|
|
76
|
+
);
|
|
77
|
+
const getTabProps = useCallback(
|
|
78
|
+
(value, options) => {
|
|
79
|
+
const isDisabled = options?.disabled ?? tabs.find((t) => t.value === value)?.disabled;
|
|
80
|
+
const isSelected = activeValue === value;
|
|
81
|
+
return {
|
|
82
|
+
id: tabId(listId, value),
|
|
83
|
+
role: "tab",
|
|
84
|
+
"aria-selected": isSelected,
|
|
85
|
+
"aria-controls": panelId(listId, value),
|
|
86
|
+
"aria-disabled": isDisabled || void 0,
|
|
87
|
+
tabIndex: isSelected ? 0 : -1,
|
|
88
|
+
disabled: isDisabled,
|
|
89
|
+
ref: (node) => {
|
|
90
|
+
if (node) {
|
|
91
|
+
tabRefs.current.set(value, node);
|
|
92
|
+
} else {
|
|
93
|
+
tabRefs.current.delete(value);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onClick: () => {
|
|
97
|
+
if (!isDisabled) setActiveValue(value);
|
|
98
|
+
},
|
|
99
|
+
onKeyDown: handleKeyDown(value)
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
[activeValue, listId, tabs, setActiveValue, handleKeyDown]
|
|
103
|
+
);
|
|
104
|
+
const getPanelProps = useCallback(
|
|
105
|
+
(value) => {
|
|
106
|
+
return {
|
|
107
|
+
id: panelId(listId, value),
|
|
108
|
+
role: "tabpanel",
|
|
109
|
+
"aria-labelledby": tabId(listId, value),
|
|
110
|
+
hidden: activeValue !== value,
|
|
111
|
+
tabIndex: 0
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
[activeValue, listId]
|
|
115
|
+
);
|
|
116
|
+
return useMemo(
|
|
117
|
+
() => ({ activeValue, getTabProps, getPanelProps, setActiveValue }),
|
|
118
|
+
[activeValue, getTabProps, getPanelProps, setActiveValue]
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { useTabs };
|
|
123
|
+
//# sourceMappingURL=chunk-UCRNSS7N.js.map
|
|
124
|
+
//# sourceMappingURL=chunk-UCRNSS7N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/useTabs.ts
|
|
6
|
+
var tabId = (listId, value) => `${listId}-tab-${value}`;
|
|
7
|
+
var panelId = (listId, value) => `${listId}-panel-${value}`;
|
|
8
|
+
var counter = 0;
|
|
9
|
+
function useStableId() {
|
|
10
|
+
const ref = react.useRef(null);
|
|
11
|
+
if (ref.current === null) {
|
|
12
|
+
ref.current = `rtabs-${++counter}`;
|
|
13
|
+
}
|
|
14
|
+
return ref.current;
|
|
15
|
+
}
|
|
16
|
+
function useTabs(opts = {}) {
|
|
17
|
+
const { tabs = [], value: controlledValue, defaultValue, onChange } = opts;
|
|
18
|
+
const listId = useStableId();
|
|
19
|
+
const isControlled = controlledValue !== void 0;
|
|
20
|
+
const [internalValue, setInternalValue] = react.useState(
|
|
21
|
+
defaultValue
|
|
22
|
+
);
|
|
23
|
+
const activeValue = isControlled ? controlledValue : internalValue;
|
|
24
|
+
const tabRefs = react.useRef(/* @__PURE__ */ new Map());
|
|
25
|
+
const setActiveValue = react.useCallback(
|
|
26
|
+
(next) => {
|
|
27
|
+
const tab = tabs.find((t) => t.value === next);
|
|
28
|
+
if (tab?.disabled) return;
|
|
29
|
+
if (isControlled) {
|
|
30
|
+
if (next !== controlledValue) onChange?.(next);
|
|
31
|
+
} else {
|
|
32
|
+
if (next === internalValue) return;
|
|
33
|
+
setInternalValue(next);
|
|
34
|
+
onChange?.(next);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[tabs, isControlled, controlledValue, internalValue, onChange]
|
|
38
|
+
);
|
|
39
|
+
const focusTab = react.useCallback((value) => {
|
|
40
|
+
tabRefs.current.get(value)?.focus();
|
|
41
|
+
}, []);
|
|
42
|
+
const handleKeyDown = react.useCallback(
|
|
43
|
+
(currentValue) => (event) => {
|
|
44
|
+
const enabled = tabs.filter((t) => !t.disabled);
|
|
45
|
+
if (enabled.length === 0) return;
|
|
46
|
+
const currentIndex = enabled.findIndex((t) => t.value === currentValue);
|
|
47
|
+
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;
|
|
71
|
+
}
|
|
72
|
+
if (nextValue === void 0 || nextValue === currentValue) return;
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
setActiveValue(nextValue);
|
|
75
|
+
focusTab(nextValue);
|
|
76
|
+
},
|
|
77
|
+
[tabs, setActiveValue, focusTab]
|
|
78
|
+
);
|
|
79
|
+
const getTabProps = react.useCallback(
|
|
80
|
+
(value, options) => {
|
|
81
|
+
const isDisabled = options?.disabled ?? tabs.find((t) => t.value === value)?.disabled;
|
|
82
|
+
const isSelected = activeValue === value;
|
|
83
|
+
return {
|
|
84
|
+
id: tabId(listId, value),
|
|
85
|
+
role: "tab",
|
|
86
|
+
"aria-selected": isSelected,
|
|
87
|
+
"aria-controls": panelId(listId, value),
|
|
88
|
+
"aria-disabled": isDisabled || void 0,
|
|
89
|
+
tabIndex: isSelected ? 0 : -1,
|
|
90
|
+
disabled: isDisabled,
|
|
91
|
+
ref: (node) => {
|
|
92
|
+
if (node) {
|
|
93
|
+
tabRefs.current.set(value, node);
|
|
94
|
+
} else {
|
|
95
|
+
tabRefs.current.delete(value);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
onClick: () => {
|
|
99
|
+
if (!isDisabled) setActiveValue(value);
|
|
100
|
+
},
|
|
101
|
+
onKeyDown: handleKeyDown(value)
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
[activeValue, listId, tabs, setActiveValue, handleKeyDown]
|
|
105
|
+
);
|
|
106
|
+
const getPanelProps = react.useCallback(
|
|
107
|
+
(value) => {
|
|
108
|
+
return {
|
|
109
|
+
id: panelId(listId, value),
|
|
110
|
+
role: "tabpanel",
|
|
111
|
+
"aria-labelledby": tabId(listId, value),
|
|
112
|
+
hidden: activeValue !== value,
|
|
113
|
+
tabIndex: 0
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
[activeValue, listId]
|
|
117
|
+
);
|
|
118
|
+
return react.useMemo(
|
|
119
|
+
() => ({ activeValue, getTabProps, getPanelProps, setActiveValue }),
|
|
120
|
+
[activeValue, getTabProps, getPanelProps, setActiveValue]
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
exports.useTabs = useTabs;
|
|
125
|
+
//# sourceMappingURL=index.cjs.map
|
|
126
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseTabsTab {
|
|
4
|
+
value: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface UseTabsOptions {
|
|
8
|
+
/** Tab definitions — needed for keyboard navigation between tabs. */
|
|
9
|
+
tabs?: UseTabsTab[];
|
|
10
|
+
/** Controlled active value. */
|
|
11
|
+
value?: string;
|
|
12
|
+
/** Initial active value when uncontrolled. */
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
/** Called when the active tab changes. */
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
|
+
}
|
|
17
|
+
interface TabProps extends HTMLAttributes<HTMLButtonElement> {
|
|
18
|
+
id: string;
|
|
19
|
+
role: "tab";
|
|
20
|
+
"aria-selected": boolean;
|
|
21
|
+
"aria-controls": string;
|
|
22
|
+
"aria-disabled"?: boolean;
|
|
23
|
+
tabIndex: number;
|
|
24
|
+
}
|
|
25
|
+
interface PanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
26
|
+
id: string;
|
|
27
|
+
role: "tabpanel";
|
|
28
|
+
"aria-labelledby": string;
|
|
29
|
+
hidden: boolean;
|
|
30
|
+
}
|
|
31
|
+
interface UseTabsResult {
|
|
32
|
+
/** Currently active tab value. */
|
|
33
|
+
activeValue: string | undefined;
|
|
34
|
+
/** Returns props to spread onto a tab trigger `<button>`. */
|
|
35
|
+
getTabProps: (value: string, options?: {
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
}) => TabProps;
|
|
38
|
+
/** Returns props to spread onto a tab panel. */
|
|
39
|
+
getPanelProps: (value: string) => PanelProps;
|
|
40
|
+
/** Programmatically set the active tab. */
|
|
41
|
+
setActiveValue: (value: string) => void;
|
|
42
|
+
}
|
|
43
|
+
declare function useTabs(opts?: UseTabsOptions): UseTabsResult;
|
|
44
|
+
|
|
45
|
+
export { type PanelProps, type TabProps, type UseTabsOptions, type UseTabsResult, type UseTabsTab, useTabs };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseTabsTab {
|
|
4
|
+
value: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface UseTabsOptions {
|
|
8
|
+
/** Tab definitions — needed for keyboard navigation between tabs. */
|
|
9
|
+
tabs?: UseTabsTab[];
|
|
10
|
+
/** Controlled active value. */
|
|
11
|
+
value?: string;
|
|
12
|
+
/** Initial active value when uncontrolled. */
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
/** Called when the active tab changes. */
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
|
+
}
|
|
17
|
+
interface TabProps extends HTMLAttributes<HTMLButtonElement> {
|
|
18
|
+
id: string;
|
|
19
|
+
role: "tab";
|
|
20
|
+
"aria-selected": boolean;
|
|
21
|
+
"aria-controls": string;
|
|
22
|
+
"aria-disabled"?: boolean;
|
|
23
|
+
tabIndex: number;
|
|
24
|
+
}
|
|
25
|
+
interface PanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
26
|
+
id: string;
|
|
27
|
+
role: "tabpanel";
|
|
28
|
+
"aria-labelledby": string;
|
|
29
|
+
hidden: boolean;
|
|
30
|
+
}
|
|
31
|
+
interface UseTabsResult {
|
|
32
|
+
/** Currently active tab value. */
|
|
33
|
+
activeValue: string | undefined;
|
|
34
|
+
/** Returns props to spread onto a tab trigger `<button>`. */
|
|
35
|
+
getTabProps: (value: string, options?: {
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
}) => TabProps;
|
|
38
|
+
/** Returns props to spread onto a tab panel. */
|
|
39
|
+
getPanelProps: (value: string) => PanelProps;
|
|
40
|
+
/** Programmatically set the active tab. */
|
|
41
|
+
setActiveValue: (value: string) => void;
|
|
42
|
+
}
|
|
43
|
+
declare function useTabs(opts?: UseTabsOptions): UseTabsResult;
|
|
44
|
+
|
|
45
|
+
export { type PanelProps, type TabProps, type UseTabsOptions, type UseTabsResult, type UseTabsTab, useTabs };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/styled.cjs
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/styled/TabsStyled.tsx
|
|
7
|
+
var tabId = (listId, value) => `${listId}-tab-${value}`;
|
|
8
|
+
var panelId = (listId, value) => `${listId}-panel-${value}`;
|
|
9
|
+
var counter = 0;
|
|
10
|
+
function useStableId() {
|
|
11
|
+
const ref = react.useRef(null);
|
|
12
|
+
if (ref.current === null) {
|
|
13
|
+
ref.current = `rtabs-${++counter}`;
|
|
14
|
+
}
|
|
15
|
+
return ref.current;
|
|
16
|
+
}
|
|
17
|
+
function useTabs(opts = {}) {
|
|
18
|
+
const { tabs = [], value: controlledValue, defaultValue, onChange } = opts;
|
|
19
|
+
const listId = useStableId();
|
|
20
|
+
const isControlled = controlledValue !== void 0;
|
|
21
|
+
const [internalValue, setInternalValue] = react.useState(
|
|
22
|
+
defaultValue
|
|
23
|
+
);
|
|
24
|
+
const activeValue = isControlled ? controlledValue : internalValue;
|
|
25
|
+
const tabRefs = react.useRef(/* @__PURE__ */ new Map());
|
|
26
|
+
const setActiveValue = react.useCallback(
|
|
27
|
+
(next) => {
|
|
28
|
+
const tab = tabs.find((t) => t.value === next);
|
|
29
|
+
if (tab?.disabled) return;
|
|
30
|
+
if (isControlled) {
|
|
31
|
+
if (next !== controlledValue) onChange?.(next);
|
|
32
|
+
} else {
|
|
33
|
+
if (next === internalValue) return;
|
|
34
|
+
setInternalValue(next);
|
|
35
|
+
onChange?.(next);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
[tabs, isControlled, controlledValue, internalValue, onChange]
|
|
39
|
+
);
|
|
40
|
+
const focusTab = react.useCallback((value) => {
|
|
41
|
+
tabRefs.current.get(value)?.focus();
|
|
42
|
+
}, []);
|
|
43
|
+
const handleKeyDown = react.useCallback(
|
|
44
|
+
(currentValue) => (event) => {
|
|
45
|
+
const enabled = tabs.filter((t) => !t.disabled);
|
|
46
|
+
if (enabled.length === 0) return;
|
|
47
|
+
const currentIndex = enabled.findIndex((t) => t.value === currentValue);
|
|
48
|
+
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;
|
|
72
|
+
}
|
|
73
|
+
if (nextValue === void 0 || nextValue === currentValue) return;
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
setActiveValue(nextValue);
|
|
76
|
+
focusTab(nextValue);
|
|
77
|
+
},
|
|
78
|
+
[tabs, setActiveValue, focusTab]
|
|
79
|
+
);
|
|
80
|
+
const getTabProps = react.useCallback(
|
|
81
|
+
(value, options) => {
|
|
82
|
+
const isDisabled = options?.disabled ?? tabs.find((t) => t.value === value)?.disabled;
|
|
83
|
+
const isSelected = activeValue === value;
|
|
84
|
+
return {
|
|
85
|
+
id: tabId(listId, value),
|
|
86
|
+
role: "tab",
|
|
87
|
+
"aria-selected": isSelected,
|
|
88
|
+
"aria-controls": panelId(listId, value),
|
|
89
|
+
"aria-disabled": isDisabled || void 0,
|
|
90
|
+
tabIndex: isSelected ? 0 : -1,
|
|
91
|
+
disabled: isDisabled,
|
|
92
|
+
ref: (node) => {
|
|
93
|
+
if (node) {
|
|
94
|
+
tabRefs.current.set(value, node);
|
|
95
|
+
} else {
|
|
96
|
+
tabRefs.current.delete(value);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
onClick: () => {
|
|
100
|
+
if (!isDisabled) setActiveValue(value);
|
|
101
|
+
},
|
|
102
|
+
onKeyDown: handleKeyDown(value)
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
[activeValue, listId, tabs, setActiveValue, handleKeyDown]
|
|
106
|
+
);
|
|
107
|
+
const getPanelProps = react.useCallback(
|
|
108
|
+
(value) => {
|
|
109
|
+
return {
|
|
110
|
+
id: panelId(listId, value),
|
|
111
|
+
role: "tabpanel",
|
|
112
|
+
"aria-labelledby": tabId(listId, value),
|
|
113
|
+
hidden: activeValue !== value,
|
|
114
|
+
tabIndex: 0
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
[activeValue, listId]
|
|
118
|
+
);
|
|
119
|
+
return react.useMemo(
|
|
120
|
+
() => ({ activeValue, getTabProps, getPanelProps, setActiveValue }),
|
|
121
|
+
[activeValue, getTabProps, getPanelProps, setActiveValue]
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
var useIsoLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
|
|
125
|
+
function useIndicator(tabListRef, activeValue) {
|
|
126
|
+
const [style, setStyle] = react.useState({});
|
|
127
|
+
useIsoLayoutEffect(() => {
|
|
128
|
+
if (!tabListRef.current || activeValue === void 0) return;
|
|
129
|
+
const measure = () => {
|
|
130
|
+
const list = tabListRef.current;
|
|
131
|
+
if (!list) return;
|
|
132
|
+
const activeTab = list.querySelector(
|
|
133
|
+
`[aria-selected="true"]`
|
|
134
|
+
);
|
|
135
|
+
if (!activeTab) return;
|
|
136
|
+
const listRect = list.getBoundingClientRect();
|
|
137
|
+
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
|
+
});
|
|
145
|
+
};
|
|
146
|
+
measure();
|
|
147
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
148
|
+
const ro = new ResizeObserver(measure);
|
|
149
|
+
ro.observe(tabListRef.current);
|
|
150
|
+
return () => ro.disconnect();
|
|
151
|
+
}, [activeValue, tabListRef]);
|
|
152
|
+
return style;
|
|
153
|
+
}
|
|
154
|
+
var TabsStyled = react.forwardRef(
|
|
155
|
+
function TabsStyled2({
|
|
156
|
+
tabs,
|
|
157
|
+
variant = "line",
|
|
158
|
+
size = "md",
|
|
159
|
+
tone = "neutral",
|
|
160
|
+
defaultValue,
|
|
161
|
+
value,
|
|
162
|
+
onChange,
|
|
163
|
+
className
|
|
164
|
+
}, ref) {
|
|
165
|
+
const tabDefs = tabs.map((t) => ({ value: t.value, disabled: t.disabled }));
|
|
166
|
+
const resolvedDefault = defaultValue ?? tabs.find((t) => !t.disabled)?.value;
|
|
167
|
+
const { activeValue, getTabProps, getPanelProps } = useTabs({
|
|
168
|
+
tabs: tabDefs,
|
|
169
|
+
defaultValue: value === void 0 ? resolvedDefault : void 0,
|
|
170
|
+
value,
|
|
171
|
+
onChange
|
|
172
|
+
});
|
|
173
|
+
const tabListRef = react.useRef(null);
|
|
174
|
+
const indicatorStyle = useIndicator(tabListRef, activeValue);
|
|
175
|
+
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",
|
|
190
|
+
{
|
|
191
|
+
className: "rtab-tab",
|
|
192
|
+
...getTabProps(tab.value, { disabled: tab.disabled }),
|
|
193
|
+
children: tab.label
|
|
194
|
+
},
|
|
195
|
+
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
|
+
] });
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
exports.TabsStyled = TabsStyled;
|
|
215
|
+
//# sourceMappingURL=styled.cjs.map
|
|
216
|
+
//# sourceMappingURL=styled.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useTabs.ts","../src/styled/TabsStyled.tsx"],"names":["useRef","useState","useCallback","useMemo","useLayoutEffect","useEffect","forwardRef","TabsStyled","jsxs","jsx"],"mappings":";;;;;;AAqDA,IAAM,QAAQ,CAAC,MAAA,EAAgB,UAAkB,CAAA,EAAG,MAAM,QAAQ,KAAK,CAAA,CAAA;AACvE,IAAM,UAAU,CAAC,MAAA,EAAgB,UAAkB,CAAA,EAAG,MAAM,UAAU,KAAK,CAAA,CAAA;AAE3E,IAAI,OAAA,GAAU,CAAA;AACd,SAAS,WAAA,GAAc;AACrB,EAAA,MAAM,GAAA,GAAMA,aAAsB,IAAI,CAAA;AACtC,EAAA,IAAI,GAAA,CAAI,YAAY,IAAA,EAAM;AACxB,IAAA,GAAA,CAAI,OAAA,GAAU,CAAA,MAAA,EAAS,EAAE,OAAO,CAAA,CAAA;AAAA,EAClC;AACA,EAAA,OAAO,GAAA,CAAI,OAAA;AACb;AAEO,SAAS,OAAA,CAAQ,IAAA,GAAuB,EAAC,EAAkB;AAChE,EAAA,MAAM,EAAE,OAAO,EAAC,EAAG,OAAO,eAAA,EAAiB,YAAA,EAAc,UAAS,GAAI,IAAA;AAEtE,EAAA,MAAM,SAAS,WAAA,EAAY;AAC3B,EAAA,MAAM,eAAe,eAAA,KAAoB,MAAA;AAEzC,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIC,cAAA;AAAA,IACxC;AAAA,GACF;AAEA,EAAA,MAAM,WAAA,GAAc,eAAe,eAAA,GAAkB,aAAA;AAErD,EAAA,MAAM,OAAA,GAAUD,YAAA,iBAAuC,IAAI,GAAA,EAAK,CAAA;AAEhE,EAAA,MAAM,cAAA,GAAiBE,iBAAA;AAAA,IACrB,CAAC,IAAA,KAAiB;AAChB,MAAA,MAAM,MAAM,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,IAAI,CAAA;AAC7C,MAAA,IAAI,KAAK,QAAA,EAAU;AACnB,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,IAAI,IAAA,KAAS,eAAA,EAAiB,QAAA,GAAW,IAAI,CAAA;AAAA,MAC/C,CAAA,MAAO;AACL,QAAA,IAAI,SAAS,aAAA,EAAe;AAC5B,QAAA,gBAAA,CAAiB,IAAI,CAAA;AACrB,QAAA,QAAA,GAAW,IAAI,CAAA;AAAA,MACjB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,IAAA,EAAM,YAAA,EAAc,eAAA,EAAiB,eAAe,QAAQ;AAAA,GAC/D;AAEA,EAAA,MAAM,QAAA,GAAWA,iBAAA,CAAY,CAAC,KAAA,KAAkB;AAC9C,IAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA,EAAG,KAAA,EAAM;AAAA,EACpC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,iBAAA;AAAA,IACpB,CAAC,YAAA,KACC,CAAC,KAAA,KAA4C;AAC3C,MAAA,MAAM,UAAU,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,EAAE,QAAQ,CAAA;AAC9C,MAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AAE1B,MAAA,MAAM,eAAe,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,YAAY,CAAA;AAEtE,MAAA,IAAI,SAAA;AAEJ,MAAA,QAAQ,MAAM,GAAA;AAAK,QACjB,KAAK,YAAA;AAAA,QACL,KAAK,WAAA,EAAa;AAChB,UAAA,MAAM,SAAA,GAAA,CAAa,YAAA,GAAe,CAAA,IAAK,OAAA,CAAQ,MAAA;AAC/C,UAAA,SAAA,GAAY,OAAA,CAAQ,SAAS,CAAA,EAAG,KAAA;AAChC,UAAA;AAAA,QACF;AAAA,QACA,KAAK,WAAA;AAAA,QACL,KAAK,SAAA,EAAW;AACd,UAAA,MAAM,SAAA,GAAA,CACH,YAAA,GAAe,CAAA,GAAI,OAAA,CAAQ,UAAU,OAAA,CAAQ,MAAA;AAChD,UAAA,SAAA,GAAY,OAAA,CAAQ,SAAS,CAAA,EAAG,KAAA;AAChC,UAAA;AAAA,QACF;AAAA,QACA,KAAK,MAAA,EAAQ;AACX,UAAA,SAAA,GAAY,OAAA,CAAQ,CAAC,CAAA,EAAG,KAAA;AACxB,UAAA;AAAA,QACF;AAAA,QACA,KAAK,KAAA,EAAO;AACV,UAAA,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,EAAG,KAAA;AACzC,UAAA;AAAA,QACF;AAAA,QACA;AACE,UAAA;AAAA;AAGJ,MAAA,IAAI,SAAA,KAAc,MAAA,IAAa,SAAA,KAAc,YAAA,EAAc;AAC3D,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,cAAA,CAAe,SAAS,CAAA;AACxB,MAAA,QAAA,CAAS,SAAS,CAAA;AAAA,IACpB,CAAA;AAAA,IACF,CAAC,IAAA,EAAM,cAAA,EAAgB,QAAQ;AAAA,GACjC;AAEA,EAAA,MAAM,WAAA,GAAcA,iBAAA;AAAA,IAClB,CAAC,OAAe,OAAA,KAA+C;AAC7D,MAAA,MAAM,UAAA,GACJ,OAAA,EAAS,QAAA,IAAY,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,KAAU,KAAK,CAAA,EAAG,QAAA;AAC5D,MAAA,MAAM,aAAa,WAAA,KAAgB,KAAA;AACnC,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA;AAAA,QACvB,IAAA,EAAM,KAAA;AAAA,QACN,eAAA,EAAiB,UAAA;AAAA,QACjB,eAAA,EAAiB,OAAA,CAAQ,MAAA,EAAQ,KAAK,CAAA;AAAA,QACtC,iBAAiB,UAAA,IAAc,MAAA;AAAA,QAC/B,QAAA,EAAU,aAAa,CAAA,GAAI,EAAA;AAAA,QAC3B,QAAA,EAAU,UAAA;AAAA,QACV,GAAA,EAAK,CAAC,IAAA,KAAmC;AACvC,UAAA,IAAI,IAAA,EAAM;AACR,YAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,KAAA,EAAO,IAAI,CAAA;AAAA,UACjC,CAAA,MAAO;AACL,YAAA,OAAA,CAAQ,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,UAC9B;AAAA,QACF,CAAA;AAAA,QACA,SAAS,MAAM;AACb,UAAA,IAAI,CAAC,UAAA,EAAY,cAAA,CAAe,KAAK,CAAA;AAAA,QACvC,CAAA;AAAA,QACA,SAAA,EAAW,cAAc,KAAK;AAAA,OAChC;AAAA,IAIF,CAAA;AAAA,IACA,CAAC,WAAA,EAAa,MAAA,EAAQ,IAAA,EAAM,gBAAgB,aAAa;AAAA,GAC3D;AAEA,EAAA,MAAM,aAAA,GAAgBA,iBAAA;AAAA,IACpB,CAAC,KAAA,KAA8B;AAC7B,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,OAAA,CAAQ,MAAA,EAAQ,KAAK,CAAA;AAAA,QACzB,IAAA,EAAM,UAAA;AAAA,QACN,iBAAA,EAAmB,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA;AAAA,QACtC,QAAQ,WAAA,KAAgB,KAAA;AAAA,QACxB,QAAA,EAAU;AAAA,OACZ;AAAA,IACF,CAAA;AAAA,IACA,CAAC,aAAa,MAAM;AAAA,GACtB;AAEA,EAAA,OAAOC,aAAA;AAAA,IACL,OAAO,EAAE,WAAA,EAAa,WAAA,EAAa,eAAe,cAAA,EAAe,CAAA;AAAA,IACjE,CAAC,WAAA,EAAa,WAAA,EAAa,aAAA,EAAe,cAAc;AAAA,GAC1D;AACF;AC7JA,IAAM,kBAAA,GACJ,OAAO,MAAA,KAAW,WAAA,GAAcC,qBAAA,GAAkBC,eAAA;AAEpD,SAAS,YAAA,CACP,YACA,WAAA,EACA;AACA,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIJ,cAAAA,CAAwB,EAAE,CAAA;AAEpD,EAAA,kBAAA,CAAmB,MAAM;AACvB,IAAA,IAAI,CAAC,UAAA,CAAW,OAAA,IAAW,WAAA,KAAgB,MAAA,EAAW;AAEtD,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,MAAM,OAAO,UAAA,CAAW,OAAA;AACxB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,MAAM,YAAY,IAAA,CAAK,aAAA;AAAA,QACrB,CAAA,sBAAA;AAAA,OACF;AACA,MAAA,IAAI,CAAC,SAAA,EAAW;AAEhB,MAAA,MAAM,QAAA,GAAW,KAAK,qBAAA,EAAsB;AAC5C,MAAA,MAAM,OAAA,GAAU,UAAU,qBAAA,EAAsB;AAChD,MAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,IAAA,GAAO,QAAA,CAAS,IAAA;AAClC,MAAA,MAAM,IAAI,OAAA,CAAQ,KAAA;AAElB,MAAA,QAAA,CAAS;AAAA,QACP,CAAC,oBAA8B,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,QACtC,CAAC,wBAAkC,GAAG,CAAA,EAAG,CAAC,CAAA,EAAA,CAAA;AAAA,QAC1C,CAAC,wBAAkC,GAAG;AAAA,OACvC,CAAA;AAAA,IACH,CAAA;AAEA,IAAA,OAAA,EAAQ;AAER,IAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAC3C,IAAA,MAAM,EAAA,GAAK,IAAI,cAAA,CAAe,OAAO,CAAA;AACrC,IAAA,EAAA,CAAG,OAAA,CAAQ,WAAW,OAAO,CAAA;AAC7B,IAAA,OAAO,MAAM,GAAG,UAAA,EAAW;AAAA,EAC7B,CAAA,EAAG,CAAC,WAAA,EAAa,UAAU,CAAC,CAAA;AAE5B,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,UAAA,GAAaK,gBAAA;AAAA,EACxB,SAASC,WAAAA,CACP;AAAA,IACE,IAAA;AAAA,IACA,OAAA,GAAU,MAAA;AAAA,IACV,IAAA,GAAO,IAAA;AAAA,IACP,IAAA,GAAO,SAAA;AAAA,IACP,YAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,KAAA,EAAO,CAAA,CAAE,KAAA,EAAO,QAAA,EAAU,CAAA,CAAE,QAAA,EAAS,CAAE,CAAA;AAE1E,IAAA,MAAM,eAAA,GACJ,gBAAgB,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,QAAQ,CAAA,EAAG,KAAA;AAEjD,IAAA,MAAM,EAAE,WAAA,EAAa,WAAA,EAAa,aAAA,KAAkB,OAAA,CAAQ;AAAA,MAC1D,IAAA,EAAM,OAAA;AAAA,MACN,YAAA,EAAc,KAAA,KAAU,MAAA,GAAY,eAAA,GAAkB,MAAA;AAAA,MACtD,KAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAA,GAAaP,aAAuB,IAAI,CAAA;AAC9C,IAAA,MAAM,cAAA,GAAiB,YAAA,CAAa,UAAA,EAAY,WAAW,CAAA;AAE3D,IAAA,MAAM,SAAA,GAAY,CAAC,WAAA,EAAa,SAAS,EAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAEnE,IAAA,uBACEQ,eAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,SAAA,EAAW,SAAA,EACxB,QAAA,EAAA;AAAA,sBAAAA,eAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,GAAA,EAAK,UAAA;AAAA,UACL,IAAA,EAAK,SAAA;AAAA,UACL,SAAA,EAAU,WAAA;AAAA,UACV,cAAA,EAAc,OAAA;AAAA,UACd,WAAA,EAAW,IAAA;AAAA,UACX,WAAA,EAAW,IAAA;AAAA,UACX,KAAA,EAAO,cAAA;AAAA,UAEN,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACTC,cAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBAEC,SAAA,EAAU,UAAA;AAAA,gBACT,GAAG,YAAY,GAAA,CAAI,KAAA,EAAO,EAAE,QAAA,EAAU,GAAA,CAAI,UAAU,CAAA;AAAA,gBAEpD,QAAA,EAAA,GAAA,CAAI;AAAA,eAAA;AAAA,cAJA,GAAA,CAAI;AAAA,aAMZ,CAAA;AAAA,YAAA,CACC,OAAA,KAAY,MAAA,IAAU,OAAA,KAAY,OAAA,IAAW,OAAA,KAAY,MAAA,qBACzDA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EAAiB,aAAA,EAAY,MAAA,EAAO;AAAA;AAAA;AAAA,OAExD;AAAA,qCACC,KAAA,EAAA,EAAI,SAAA,EAAU,eACZ,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACTA,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UAEC,SAAA,EAAU,YAAA;AAAA,UACT,GAAG,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA;AAAA,UAE1B,QAAA,EAAA,GAAA,CAAI;AAAA,SAAA;AAAA,QAJA,GAAA,CAAI;AAAA,OAMZ,CAAA,EACH;AAAA,KAAA,EACF,CAAA;AAAA,EAEJ;AACF","file":"styled.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"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type TabsVariant = "line" | "solid" | "pill";
|
|
5
|
+
type TabsSize = "sm" | "md" | "lg";
|
|
6
|
+
type TabsTone = "neutral" | "primary";
|
|
7
|
+
interface TabItem {
|
|
8
|
+
value: string;
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
content: ReactNode;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface TabsStyledProps {
|
|
14
|
+
tabs: TabItem[];
|
|
15
|
+
variant?: TabsVariant;
|
|
16
|
+
size?: TabsSize;
|
|
17
|
+
tone?: TabsTone;
|
|
18
|
+
defaultValue?: string;
|
|
19
|
+
value?: string;
|
|
20
|
+
onChange?: (value: string) => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
declare const TabsStyled: react.ForwardRefExoticComponent<TabsStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
24
|
+
|
|
25
|
+
export { type TabItem, type TabsSize, TabsStyled, type TabsStyledProps, type TabsTone, type TabsVariant };
|
package/dist/styled.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type TabsVariant = "line" | "solid" | "pill";
|
|
5
|
+
type TabsSize = "sm" | "md" | "lg";
|
|
6
|
+
type TabsTone = "neutral" | "primary";
|
|
7
|
+
interface TabItem {
|
|
8
|
+
value: string;
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
content: ReactNode;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface TabsStyledProps {
|
|
14
|
+
tabs: TabItem[];
|
|
15
|
+
variant?: TabsVariant;
|
|
16
|
+
size?: TabsSize;
|
|
17
|
+
tone?: TabsTone;
|
|
18
|
+
defaultValue?: string;
|
|
19
|
+
value?: string;
|
|
20
|
+
onChange?: (value: string) => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
declare const TabsStyled: react.ForwardRefExoticComponent<TabsStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
24
|
+
|
|
25
|
+
export { type TabItem, type TabsSize, TabsStyled, type TabsStyledProps, type TabsTone, type TabsVariant };
|
package/dist/styled.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useTabs } from './chunk-UCRNSS7N.js';
|
|
2
|
+
import { forwardRef, useRef, useState, useLayoutEffect, useEffect } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
6
|
+
function useIndicator(tabListRef, activeValue) {
|
|
7
|
+
const [style, setStyle] = useState({});
|
|
8
|
+
useIsoLayoutEffect(() => {
|
|
9
|
+
if (!tabListRef.current || activeValue === void 0) return;
|
|
10
|
+
const measure = () => {
|
|
11
|
+
const list = tabListRef.current;
|
|
12
|
+
if (!list) return;
|
|
13
|
+
const activeTab = list.querySelector(
|
|
14
|
+
`[aria-selected="true"]`
|
|
15
|
+
);
|
|
16
|
+
if (!activeTab) return;
|
|
17
|
+
const listRect = list.getBoundingClientRect();
|
|
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
|
+
});
|
|
26
|
+
};
|
|
27
|
+
measure();
|
|
28
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
29
|
+
const ro = new ResizeObserver(measure);
|
|
30
|
+
ro.observe(tabListRef.current);
|
|
31
|
+
return () => ro.disconnect();
|
|
32
|
+
}, [activeValue, tabListRef]);
|
|
33
|
+
return style;
|
|
34
|
+
}
|
|
35
|
+
var TabsStyled = forwardRef(
|
|
36
|
+
function TabsStyled2({
|
|
37
|
+
tabs,
|
|
38
|
+
variant = "line",
|
|
39
|
+
size = "md",
|
|
40
|
+
tone = "neutral",
|
|
41
|
+
defaultValue,
|
|
42
|
+
value,
|
|
43
|
+
onChange,
|
|
44
|
+
className
|
|
45
|
+
}, ref) {
|
|
46
|
+
const tabDefs = tabs.map((t) => ({ value: t.value, disabled: t.disabled }));
|
|
47
|
+
const resolvedDefault = defaultValue ?? tabs.find((t) => !t.disabled)?.value;
|
|
48
|
+
const { activeValue, getTabProps, getPanelProps } = useTabs({
|
|
49
|
+
tabs: tabDefs,
|
|
50
|
+
defaultValue: value === void 0 ? resolvedDefault : void 0,
|
|
51
|
+
value,
|
|
52
|
+
onChange
|
|
53
|
+
});
|
|
54
|
+
const tabListRef = useRef(null);
|
|
55
|
+
const indicatorStyle = useIndicator(tabListRef, activeValue);
|
|
56
|
+
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",
|
|
71
|
+
{
|
|
72
|
+
className: "rtab-tab",
|
|
73
|
+
...getTabProps(tab.value, { disabled: tab.disabled }),
|
|
74
|
+
children: tab.label
|
|
75
|
+
},
|
|
76
|
+
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
|
+
] });
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
export { TabsStyled };
|
|
96
|
+
//# sourceMappingURL=styled.js.map
|
|
97
|
+
//# sourceMappingURL=styled.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* react-tabs — styled component
|
|
3
|
+
*
|
|
4
|
+
* Themable via CSS variables. The sliding indicator uses
|
|
5
|
+
* --rtab-indicator-x / --rtab-indicator-width set by the hook on the tablist.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
.rtab-root {
|
|
9
|
+
--rtab-fg: #18181b;
|
|
10
|
+
--rtab-fg-muted: #71717a;
|
|
11
|
+
--rtab-fg-active: #18181b;
|
|
12
|
+
--rtab-fg-inactive: #52525b;
|
|
13
|
+
--rtab-bg-tab-active: transparent;
|
|
14
|
+
--rtab-bg-indicator: #18181b;
|
|
15
|
+
--rtab-border-list: #e4e4e7;
|
|
16
|
+
--rtab-ring: rgba(99, 102, 241, 0.4);
|
|
17
|
+
|
|
18
|
+
--rtab-font-size: 0.875rem;
|
|
19
|
+
--rtab-font-weight: 500;
|
|
20
|
+
--rtab-tab-padding-y: 0.5rem;
|
|
21
|
+
--rtab-tab-padding-x: 1rem;
|
|
22
|
+
|
|
23
|
+
--rtab-duration: 300ms;
|
|
24
|
+
--rtab-duration-fast: 160ms;
|
|
25
|
+
--rtab-ease: cubic-bezier(0.32, 0.72, 0, 1);
|
|
26
|
+
--rtab-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
27
|
+
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
font-family: inherit;
|
|
31
|
+
color: var(--rtab-fg);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ============================================================================
|
|
35
|
+
* Tab list
|
|
36
|
+
* ========================================================================== */
|
|
37
|
+
.rtab-list {
|
|
38
|
+
position: relative;
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: stretch;
|
|
41
|
+
isolation: isolate;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* ============================================================================
|
|
45
|
+
* Sliding indicator
|
|
46
|
+
* ========================================================================== */
|
|
47
|
+
.rtab-indicator {
|
|
48
|
+
position: absolute;
|
|
49
|
+
bottom: 0;
|
|
50
|
+
left: 0;
|
|
51
|
+
pointer-events: none;
|
|
52
|
+
z-index: 0;
|
|
53
|
+
width: var(--rtab-indicator-width, 0px);
|
|
54
|
+
transform: translateX(var(--rtab-indicator-x, 0px));
|
|
55
|
+
transition:
|
|
56
|
+
transform var(--rtab-duration) var(--rtab-ease-spring),
|
|
57
|
+
width var(--rtab-duration) var(--rtab-ease-spring),
|
|
58
|
+
background-color var(--rtab-duration-fast) var(--rtab-ease);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Hide indicator on first paint — before the layout effect measures. */
|
|
62
|
+
.rtab-list:not([style*="--rtab-indicator-ready: 1"]) .rtab-indicator {
|
|
63
|
+
opacity: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ============================================================================
|
|
67
|
+
* Tab buttons
|
|
68
|
+
* ========================================================================== */
|
|
69
|
+
.rtab-tab {
|
|
70
|
+
position: relative;
|
|
71
|
+
z-index: 1;
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
background: transparent;
|
|
76
|
+
border: none;
|
|
77
|
+
padding: var(--rtab-tab-padding-y) var(--rtab-tab-padding-x);
|
|
78
|
+
font: inherit;
|
|
79
|
+
font-size: var(--rtab-font-size);
|
|
80
|
+
font-weight: var(--rtab-font-weight);
|
|
81
|
+
line-height: 1.2;
|
|
82
|
+
color: var(--rtab-fg-inactive);
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
user-select: none;
|
|
86
|
+
-webkit-tap-highlight-color: transparent;
|
|
87
|
+
outline: none;
|
|
88
|
+
transition:
|
|
89
|
+
color var(--rtab-duration-fast) var(--rtab-ease),
|
|
90
|
+
background-color var(--rtab-duration-fast) var(--rtab-ease);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.rtab-tab[aria-selected="true"] {
|
|
94
|
+
color: var(--rtab-fg-active);
|
|
95
|
+
background: var(--rtab-bg-tab-active);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.rtab-tab:hover:not(:disabled):not([aria-selected="true"]) {
|
|
99
|
+
color: var(--rtab-fg);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.rtab-tab:disabled {
|
|
103
|
+
cursor: not-allowed;
|
|
104
|
+
opacity: 0.45;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.rtab-tab:focus-visible {
|
|
108
|
+
box-shadow: 0 0 0 2px var(--rtab-ring);
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ============================================================================
|
|
113
|
+
* Tab panels
|
|
114
|
+
* ========================================================================== */
|
|
115
|
+
.rtab-panels {
|
|
116
|
+
flex: 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.rtab-panel {
|
|
120
|
+
outline: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.rtab-panel[hidden] {
|
|
124
|
+
display: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ============================================================================
|
|
128
|
+
* Variant — line (default)
|
|
129
|
+
* ========================================================================== */
|
|
130
|
+
.rtab-list[data-variant="line"] {
|
|
131
|
+
border-bottom: 1px solid var(--rtab-border-list);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.rtab-list[data-variant="line"] .rtab-tab {
|
|
135
|
+
border-radius: 0;
|
|
136
|
+
padding-bottom: calc(var(--rtab-tab-padding-y) + 2px);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.rtab-list[data-variant="line"] .rtab-indicator {
|
|
140
|
+
height: 2px;
|
|
141
|
+
background: var(--rtab-bg-indicator);
|
|
142
|
+
border-radius: 2px 2px 0 0;
|
|
143
|
+
bottom: -1px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ============================================================================
|
|
147
|
+
* Variant — solid
|
|
148
|
+
* ========================================================================== */
|
|
149
|
+
.rtab-list[data-variant="solid"] {
|
|
150
|
+
background: #f4f4f5;
|
|
151
|
+
border-radius: 8px;
|
|
152
|
+
padding: 3px;
|
|
153
|
+
gap: 2px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.rtab-list[data-variant="solid"] .rtab-tab {
|
|
157
|
+
border-radius: 6px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.rtab-list[data-variant="solid"] .rtab-tab[aria-selected="true"] {
|
|
161
|
+
color: var(--rtab-fg-active);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.rtab-list[data-variant="solid"] .rtab-indicator {
|
|
165
|
+
top: 3px;
|
|
166
|
+
bottom: 3px;
|
|
167
|
+
height: auto;
|
|
168
|
+
background: #ffffff;
|
|
169
|
+
border-radius: 6px;
|
|
170
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ============================================================================
|
|
174
|
+
* Variant — pill
|
|
175
|
+
* ========================================================================== */
|
|
176
|
+
.rtab-list[data-variant="pill"] {
|
|
177
|
+
background: #f4f4f5;
|
|
178
|
+
border-radius: 999px;
|
|
179
|
+
padding: 3px;
|
|
180
|
+
gap: 2px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.rtab-list[data-variant="pill"] .rtab-tab {
|
|
184
|
+
border-radius: 999px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.rtab-list[data-variant="pill"] .rtab-tab[aria-selected="true"] {
|
|
188
|
+
color: var(--rtab-fg-active);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.rtab-list[data-variant="pill"] .rtab-indicator {
|
|
192
|
+
top: 3px;
|
|
193
|
+
bottom: 3px;
|
|
194
|
+
height: auto;
|
|
195
|
+
background: #ffffff;
|
|
196
|
+
border-radius: 999px;
|
|
197
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ============================================================================
|
|
201
|
+
* Sizes
|
|
202
|
+
* ========================================================================== */
|
|
203
|
+
.rtab-list[data-size="sm"] {
|
|
204
|
+
--rtab-tab-padding-y: 0.3rem;
|
|
205
|
+
--rtab-tab-padding-x: 0.75rem;
|
|
206
|
+
--rtab-font-size: 0.8rem;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.rtab-list[data-size="lg"] {
|
|
210
|
+
--rtab-tab-padding-y: 0.65rem;
|
|
211
|
+
--rtab-tab-padding-x: 1.25rem;
|
|
212
|
+
--rtab-font-size: 0.95rem;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ============================================================================
|
|
216
|
+
* Tones — doubled selector for theme-override resilience
|
|
217
|
+
* ========================================================================== */
|
|
218
|
+
.rtab-list[data-tone="primary"][data-tone] {
|
|
219
|
+
--rtab-bg-indicator: #6366f1;
|
|
220
|
+
--rtab-ring: rgba(99, 102, 241, 0.4);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.rtab-list[data-tone="primary"][data-tone][data-variant="solid"] .rtab-indicator,
|
|
224
|
+
.rtab-list[data-tone="primary"][data-tone][data-variant="pill"] .rtab-indicator {
|
|
225
|
+
background: #6366f1;
|
|
226
|
+
box-shadow: 0 4px 12px -2px rgba(99, 102, 241, 0.45);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.rtab-list[data-tone="primary"][data-tone][data-variant="solid"] .rtab-tab[aria-selected="true"],
|
|
230
|
+
.rtab-list[data-tone="primary"][data-tone][data-variant="pill"] .rtab-tab[aria-selected="true"] {
|
|
231
|
+
color: #ffffff;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* ============================================================================
|
|
235
|
+
* Dark mode (explicit override — set data-rtab-theme="dark" on a wrapper,
|
|
236
|
+
* or rely on the page root having data-theme="dark")
|
|
237
|
+
* ========================================================================== */
|
|
238
|
+
|
|
239
|
+
[data-theme="dark"] .rtab-root,
|
|
240
|
+
[data-rtab-theme="dark"] .rtab-root {
|
|
241
|
+
--rtab-fg: #fafafa;
|
|
242
|
+
--rtab-fg-muted: #a1a1aa;
|
|
243
|
+
--rtab-fg-active: #fafafa;
|
|
244
|
+
--rtab-fg-inactive: #a1a1aa;
|
|
245
|
+
--rtab-border-list: #3f3f46;
|
|
246
|
+
--rtab-bg-indicator: #fafafa;
|
|
247
|
+
--rtab-ring: rgba(129, 140, 248, 0.5);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
[data-theme="dark"] .rtab-list[data-variant="solid"],
|
|
251
|
+
[data-rtab-theme="dark"] .rtab-list[data-variant="solid"],
|
|
252
|
+
[data-theme="dark"] .rtab-list[data-variant="pill"],
|
|
253
|
+
[data-rtab-theme="dark"] .rtab-list[data-variant="pill"] {
|
|
254
|
+
background: #27272a;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
[data-theme="dark"] .rtab-list[data-variant="solid"] .rtab-indicator,
|
|
258
|
+
[data-rtab-theme="dark"] .rtab-list[data-variant="solid"] .rtab-indicator,
|
|
259
|
+
[data-theme="dark"] .rtab-list[data-variant="pill"] .rtab-indicator,
|
|
260
|
+
[data-rtab-theme="dark"] .rtab-list[data-variant="pill"] .rtab-indicator {
|
|
261
|
+
background: #3f3f46;
|
|
262
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
[data-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="solid"] .rtab-indicator,
|
|
266
|
+
[data-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="pill"] .rtab-indicator,
|
|
267
|
+
[data-rtab-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="solid"] .rtab-indicator,
|
|
268
|
+
[data-rtab-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="pill"] .rtab-indicator {
|
|
269
|
+
background: #6366f1;
|
|
270
|
+
box-shadow: 0 4px 14px -2px rgba(99, 102, 241, 0.5);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
[data-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="solid"] .rtab-tab[aria-selected="true"],
|
|
274
|
+
[data-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="pill"] .rtab-tab[aria-selected="true"],
|
|
275
|
+
[data-rtab-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="solid"] .rtab-tab[aria-selected="true"],
|
|
276
|
+
[data-rtab-theme="dark"] .rtab-list[data-tone="primary"][data-tone][data-variant="pill"] .rtab-tab[aria-selected="true"] {
|
|
277
|
+
color: #ffffff;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
[data-rtab-theme="light"] .rtab-root {
|
|
281
|
+
--rtab-fg: #18181b;
|
|
282
|
+
--rtab-fg-muted: #71717a;
|
|
283
|
+
--rtab-fg-active: #18181b;
|
|
284
|
+
--rtab-fg-inactive: #52525b;
|
|
285
|
+
--rtab-border-list: #e4e4e7;
|
|
286
|
+
--rtab-bg-indicator: #18181b;
|
|
287
|
+
--rtab-ring: rgba(99, 102, 241, 0.4);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* ============================================================================
|
|
291
|
+
* Reduced motion
|
|
292
|
+
* ========================================================================== */
|
|
293
|
+
@media (prefers-reduced-motion: reduce) {
|
|
294
|
+
.rtab-indicator,
|
|
295
|
+
.rtab-tab {
|
|
296
|
+
transition-duration: 0ms !important;
|
|
297
|
+
}
|
|
298
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mshafiqyajid/react-tabs",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Headless tabs hook and styled component for React. Accessible, keyboard-friendly, animated, SSR-safe, fully typed.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"tabs",
|
|
8
|
+
"accessible",
|
|
9
|
+
"headless",
|
|
10
|
+
"hook",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Shafiq Yajid",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/mshafiqyajid/packages.git",
|
|
18
|
+
"directory": "packages/react-tabs"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/mshafiqyajid/packages/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://docs.shafiqyajid.com/react/tabs/",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.cjs",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./styled": {
|
|
35
|
+
"types": "./dist/styled.d.ts",
|
|
36
|
+
"import": "./dist/styled.js",
|
|
37
|
+
"require": "./dist/styled.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./styles.css": "./dist/styles.css",
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"sideEffects": [
|
|
48
|
+
"**/*.css"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"prepublishOnly": "npm run build"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": ">=17.0.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@testing-library/jest-dom": "^6.4.0",
|
|
63
|
+
"@testing-library/react": "^16.0.0",
|
|
64
|
+
"@testing-library/user-event": "^14.5.0",
|
|
65
|
+
"@types/node": "^22.0.0",
|
|
66
|
+
"@types/react": "^18.3.0",
|
|
67
|
+
"@types/react-dom": "^18.3.0",
|
|
68
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
69
|
+
"jsdom": "^25.0.0",
|
|
70
|
+
"react": "^18.3.0",
|
|
71
|
+
"react-dom": "^18.3.0",
|
|
72
|
+
"tsup": "^8.3.0",
|
|
73
|
+
"typescript": "^5.6.0",
|
|
74
|
+
"vitest": "^2.1.0"
|
|
75
|
+
},
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public"
|
|
78
|
+
},
|
|
79
|
+
"engines": {
|
|
80
|
+
"node": ">=18"
|
|
81
|
+
}
|
|
82
|
+
}
|