eidos-ui 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-RAZRQ57B.js → chunk-74MBKLYI.js} +97 -16
- package/dist/chunk-74MBKLYI.js.map +1 -0
- package/dist/{chunk-VHQTDITV.cjs → chunk-KQD7CQ4Z.cjs} +96 -15
- package/dist/chunk-KQD7CQ4Z.cjs.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.css +159 -21
- package/dist/index.js +1 -1
- package/dist/tabs/index.cjs +2 -2
- package/dist/tabs/index.d.cts +11 -0
- package/dist/tabs/index.d.ts +11 -0
- package/dist/tabs/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-RAZRQ57B.js.map +0 -1
- package/dist/chunk-VHQTDITV.cjs.map +0 -1
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
} from "./chunk-KBG77JHC.js";
|
|
4
4
|
|
|
5
5
|
// src/components/Tabs/Tabs.component.tsx
|
|
6
|
-
import React, { useState, useRef, useContext, useLayoutEffect } from "react";
|
|
6
|
+
import React, { useState, useRef, useContext, useEffect, useLayoutEffect } from "react";
|
|
7
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
var TabsContext = React.createContext(null);
|
|
9
10
|
var useTabsContext = () => {
|
|
@@ -93,12 +94,23 @@ var Tabs = ({
|
|
|
93
94
|
size = "md",
|
|
94
95
|
color = "primary",
|
|
95
96
|
fullWidth = false,
|
|
97
|
+
scrollButtons = "auto",
|
|
96
98
|
className = "",
|
|
97
99
|
children
|
|
98
100
|
}) => {
|
|
99
101
|
const isControlled = value !== void 0;
|
|
100
102
|
const [localValue, setLocalValue] = useState(defaultValue ?? "");
|
|
101
103
|
const activeValue = isControlled ? value : localValue;
|
|
104
|
+
const tabChildren = [];
|
|
105
|
+
const panelChildren = [];
|
|
106
|
+
React.Children.forEach(children, (child) => {
|
|
107
|
+
if (!React.isValidElement(child)) return;
|
|
108
|
+
if (child.type === Tab) {
|
|
109
|
+
tabChildren.push(child);
|
|
110
|
+
} else if (child.type === TabPanel) {
|
|
111
|
+
panelChildren.push(child);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
102
114
|
const listRef = useRef(null);
|
|
103
115
|
const indicatorRef = useRef(null);
|
|
104
116
|
const hasPositioned = useRef(false);
|
|
@@ -120,28 +132,73 @@ var Tabs = ({
|
|
|
120
132
|
hasPositioned.current = true;
|
|
121
133
|
}
|
|
122
134
|
}, [activeValue, variant, fullWidth]);
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
const list = listRef.current;
|
|
137
|
+
if (!list) return;
|
|
138
|
+
const activeTab = list.querySelector('[aria-selected="true"]');
|
|
139
|
+
if (!activeTab) return;
|
|
140
|
+
const left = activeTab.offsetLeft;
|
|
141
|
+
const right = left + activeTab.offsetWidth;
|
|
142
|
+
if (left < list.scrollLeft) {
|
|
143
|
+
list.scrollLeft = left;
|
|
144
|
+
} else if (right > list.scrollLeft + list.clientWidth) {
|
|
145
|
+
list.scrollLeft = right - list.clientWidth;
|
|
146
|
+
}
|
|
147
|
+
}, [activeValue]);
|
|
148
|
+
const [scrollState, setScrollState] = useState({ canScrollPrev: false, canScrollNext: false });
|
|
149
|
+
const hasScrollButtons = scrollButtons !== "none";
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (!hasScrollButtons) return;
|
|
152
|
+
const list = listRef.current;
|
|
153
|
+
if (!list) return;
|
|
154
|
+
const update = () => {
|
|
155
|
+
const maxScroll = list.scrollWidth - list.clientWidth;
|
|
156
|
+
const canScrollPrev = list.scrollLeft > 1;
|
|
157
|
+
const canScrollNext = list.scrollLeft < maxScroll - 1;
|
|
158
|
+
setScrollState(
|
|
159
|
+
(previous) => previous.canScrollPrev === canScrollPrev && previous.canScrollNext === canScrollNext ? previous : { canScrollPrev, canScrollNext }
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
update();
|
|
163
|
+
list.addEventListener("scroll", update, { passive: true });
|
|
164
|
+
const observer = new ResizeObserver(update);
|
|
165
|
+
observer.observe(list);
|
|
166
|
+
Array.from(list.children).forEach((child) => observer.observe(child));
|
|
167
|
+
return () => {
|
|
168
|
+
list.removeEventListener("scroll", update);
|
|
169
|
+
observer.disconnect();
|
|
170
|
+
};
|
|
171
|
+
}, [hasScrollButtons, tabChildren.length, size, variant, fullWidth]);
|
|
172
|
+
const scrollByStep = (direction) => {
|
|
173
|
+
const list = listRef.current;
|
|
174
|
+
if (!list) return;
|
|
175
|
+
const step = list.clientWidth * 0.8;
|
|
176
|
+
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
177
|
+
list.scrollBy({ left: direction * step, behavior: reducedMotion ? "auto" : "smooth" });
|
|
178
|
+
};
|
|
123
179
|
const onSelect = (newValue) => {
|
|
124
180
|
if (!isControlled) {
|
|
125
181
|
setLocalValue(newValue);
|
|
126
182
|
}
|
|
127
183
|
onChange?.(newValue);
|
|
128
184
|
};
|
|
129
|
-
const tabChildren = [];
|
|
130
|
-
const panelChildren = [];
|
|
131
|
-
React.Children.forEach(children, (child) => {
|
|
132
|
-
if (!React.isValidElement(child)) return;
|
|
133
|
-
if (child.type === Tab) {
|
|
134
|
-
tabChildren.push(child);
|
|
135
|
-
} else if (child.type === TabPanel) {
|
|
136
|
-
panelChildren.push(child);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
185
|
const rootClasses = ["eidos-tabs", `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className].filter(Boolean).join(" ");
|
|
140
186
|
const listClasses = [
|
|
141
187
|
"eidos-tabs-list",
|
|
142
188
|
`eidos-tabs-list--${size}`,
|
|
143
|
-
fullWidth && "eidos-tabs-list--fullWidth"
|
|
189
|
+
fullWidth && "eidos-tabs-list--fullWidth",
|
|
190
|
+
// With buttons, they are the affordance - a native scrollbar underneath
|
|
191
|
+
// them is redundant, and on a space-taking one it would also sit over the
|
|
192
|
+
// `line` variant's rule.
|
|
193
|
+
hasScrollButtons && "eidos-tabs-list--hideScrollbar"
|
|
144
194
|
].filter(Boolean).join(" ");
|
|
195
|
+
const showPrevButton = hasScrollButtons && scrollState.canScrollPrev;
|
|
196
|
+
const showNextButton = hasScrollButtons && scrollState.canScrollNext;
|
|
197
|
+
const scrollButtonClasses = (direction) => [
|
|
198
|
+
"eidos-tabs-scroll-button",
|
|
199
|
+
`eidos-tabs-scroll-button--${direction}`,
|
|
200
|
+
`eidos-tabs-scroll-button--${size}`
|
|
201
|
+
].join(" ");
|
|
145
202
|
const contextValue = {
|
|
146
203
|
activeValue,
|
|
147
204
|
onSelect,
|
|
@@ -152,9 +209,33 @@ var Tabs = ({
|
|
|
152
209
|
listRef
|
|
153
210
|
};
|
|
154
211
|
return /* @__PURE__ */ jsx(TabsContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs("div", { className: rootClasses, children: [
|
|
155
|
-
/* @__PURE__ */ jsxs("div", {
|
|
156
|
-
|
|
157
|
-
|
|
212
|
+
/* @__PURE__ */ jsxs("div", { className: "eidos-tabs-bar", children: [
|
|
213
|
+
showPrevButton && /* @__PURE__ */ jsx(
|
|
214
|
+
"button",
|
|
215
|
+
{
|
|
216
|
+
type: "button",
|
|
217
|
+
className: scrollButtonClasses("prev"),
|
|
218
|
+
onClick: () => scrollByStep(-1),
|
|
219
|
+
tabIndex: -1,
|
|
220
|
+
"aria-hidden": "true",
|
|
221
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "eidos-tabs-scroll-button-icon" })
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
/* @__PURE__ */ jsxs("div", { ref: listRef, className: listClasses, role: "tablist", children: [
|
|
225
|
+
tabChildren,
|
|
226
|
+
variant === "line" && /* @__PURE__ */ jsx("span", { ref: indicatorRef, className: "eidos-tabs-indicator", "aria-hidden": "true" })
|
|
227
|
+
] }),
|
|
228
|
+
showNextButton && /* @__PURE__ */ jsx(
|
|
229
|
+
"button",
|
|
230
|
+
{
|
|
231
|
+
type: "button",
|
|
232
|
+
className: scrollButtonClasses("next"),
|
|
233
|
+
onClick: () => scrollByStep(1),
|
|
234
|
+
tabIndex: -1,
|
|
235
|
+
"aria-hidden": "true",
|
|
236
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "eidos-tabs-scroll-button-icon" })
|
|
237
|
+
}
|
|
238
|
+
)
|
|
158
239
|
] }),
|
|
159
240
|
/* @__PURE__ */ jsx("div", { className: "eidos-tabs-panels", children: panelChildren })
|
|
160
241
|
] }) });
|
|
@@ -166,4 +247,4 @@ export {
|
|
|
166
247
|
TabPanel,
|
|
167
248
|
Tabs
|
|
168
249
|
};
|
|
169
|
-
//# sourceMappingURL=chunk-
|
|
250
|
+
//# sourceMappingURL=chunk-74MBKLYI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Tabs/Tabs.component.tsx"],"sourcesContent":["import React, { useState, useRef, useContext, useEffect, useLayoutEffect } from 'react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { renderIcon } from '../../utils';\nimport type { TabsProps, TabProps, TabPanelProps, TabsContextValue } from './Tabs.types';\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null);\n\nconst useTabsContext = (): TabsContextValue => {\n const ctx = useContext(TabsContext);\n if (!ctx) throw new Error('Tab/TabPanel must be used inside <Tabs>');\n return ctx;\n};\n\n// ============================================================================\n// TAB - individual tab trigger button\n// Defined before Tabs so Tabs can use `child.type === Tab` for child separation.\n// ============================================================================\n\nexport const Tab: React.FC<TabProps> = ({\n value,\n children,\n disabled = false,\n icon,\n className = '',\n}) => {\n const { activeValue, onSelect, size, fullWidth, listRef } = useTabsContext();\n const isActive = activeValue === value;\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n const list = listRef.current;\n if (!list) return;\n\n const allTabs = Array.from(\n list.querySelectorAll('[role=\"tab\"]:not([disabled])'),\n ) as HTMLElement[];\n const currentIndex = allTabs.indexOf(e.currentTarget);\n if (currentIndex === -1) return;\n\n let targetIndex = -1;\n if (e.key === 'ArrowRight') targetIndex = (currentIndex + 1) % allTabs.length;\n else if (e.key === 'ArrowLeft')\n targetIndex = (currentIndex - 1 + allTabs.length) % allTabs.length;\n else if (e.key === 'Home') targetIndex = 0;\n else if (e.key === 'End') targetIndex = allTabs.length - 1;\n\n if (targetIndex !== -1) {\n e.preventDefault();\n allTabs[targetIndex].focus();\n }\n };\n\n const tabClasses = [\n 'eidos-tab',\n `eidos-tab--${size}`,\n isActive && 'eidos-tab--active',\n disabled && 'eidos-tab--disabled',\n fullWidth && 'eidos-tab--fullWidth',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <button\n id={`eidos-tab-${value}`}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`eidos-tabpanel-${value}`}\n tabIndex={isActive ? 0 : -1}\n disabled={disabled}\n className={tabClasses}\n onClick={() => !disabled && onSelect(value)}\n onKeyDown={handleKeyDown}\n type=\"button\"\n >\n {icon && <span className=\"eidos-tab-icon\">{renderIcon(icon, 'eidos-tab-icon-svg')}</span>}\n <span className=\"eidos-tab-label\">{children}</span>\n </button>\n );\n};\n\nTab.displayName = 'Tab';\n\n// ============================================================================\n// TAB PANEL - content region shown when its value matches the active tab\n// Defined before Tabs for the same `child.type === TabPanel` check.\n// ============================================================================\n\nexport const TabPanel: React.FC<TabPanelProps> = ({ value, children, className = '' }) => {\n const { activeValue } = useTabsContext();\n const isActive = activeValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n id={`eidos-tabpanel-${value}`}\n role=\"tabpanel\"\n aria-labelledby={`eidos-tab-${value}`}\n tabIndex={0}\n className={['eidos-tab-panel', className].filter(Boolean).join(' ')}\n >\n {children}\n </div>\n );\n};\n\nTabPanel.displayName = 'TabPanel';\n\n// ============================================================================\n// TABS - root container; provides context, separates tab/panel children\n// ============================================================================\n\nexport const Tabs: React.FC<TabsProps> = ({\n value,\n defaultValue,\n onChange,\n variant = 'line',\n size = 'md',\n color = 'primary',\n fullWidth = false,\n scrollButtons = 'auto',\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n const [localValue, setLocalValue] = useState(defaultValue ?? '');\n const activeValue = isControlled ? value! : localValue;\n\n // Separate Tab and TabPanel children so the list and panels render in\n // distinct DOM regions, regardless of how the consumer orders them.\n const tabChildren: React.ReactElement[] = [];\n const panelChildren: React.ReactElement[] = [];\n\n React.Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return;\n if (child.type === Tab) {\n tabChildren.push(child);\n } else if (child.type === TabPanel) {\n panelChildren.push(child);\n }\n });\n\n const listRef = useRef<HTMLDivElement>(null);\n const indicatorRef = useRef<HTMLSpanElement>(null);\n // Tracks whether the indicator has been positioned at least once so we can\n // skip the CSS transition on the very first render and avoid it animating\n // from the top-left corner to the initial tab.\n const hasPositioned = useRef(false);\n\n useLayoutEffect(() => {\n if (variant !== 'line') return;\n const list = listRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n // Suppress the CSS transition on the initial paint so the indicator snaps\n // into place rather than sliding from 0.\n if (!hasPositioned.current) {\n indicator.style.transition = 'none';\n }\n\n indicator.style.left = `${activeTab.offsetLeft}px`;\n indicator.style.width = `${activeTab.offsetWidth}px`;\n\n if (!hasPositioned.current) {\n // Force a synchronous reflow so the browser commits the no-transition\n // position before we re-enable the transition for future clicks.\n void indicator.offsetWidth;\n indicator.style.transition = '';\n hasPositioned.current = true;\n }\n }, [activeValue, variant, fullWidth]);\n\n // The list scrolls horizontally when the strip is wider than its container,\n // so an activated tab can sit outside the scrollport - reachable by keyboard\n // (`End`) or by a controlled `value` change, but invisible. `scrollLeft` is\n // adjusted directly rather than via `scrollIntoView`, which walks the whole\n // ancestor chain and would scroll the page vertically when the strip is below\n // the fold.\n useLayoutEffect(() => {\n const list = listRef.current;\n if (!list) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n const left = activeTab.offsetLeft;\n const right = left + activeTab.offsetWidth;\n\n if (left < list.scrollLeft) {\n list.scrollLeft = left;\n } else if (right > list.scrollLeft + list.clientWidth) {\n list.scrollLeft = right - list.clientWidth;\n }\n }, [activeValue]);\n\n // Whether either end of the strip has content hidden past it. Drives both\n // the presence of the scroll buttons (neither end scrollable = the strip\n // fits, so no buttons) and their disabled state at the extremes.\n const [scrollState, setScrollState] = useState({ canScrollPrev: false, canScrollNext: false });\n const hasScrollButtons = scrollButtons !== 'none';\n\n useEffect(() => {\n if (!hasScrollButtons) return;\n const list = listRef.current;\n if (!list) return;\n\n const update = () => {\n const maxScroll = list.scrollWidth - list.clientWidth;\n // 1px of tolerance: fractional layout widths leave a sub-pixel remainder\n // at either end that would otherwise keep a button enabled for a scroll\n // that can no longer move anything.\n const canScrollPrev = list.scrollLeft > 1;\n const canScrollNext = list.scrollLeft < maxScroll - 1;\n setScrollState((previous) =>\n previous.canScrollPrev === canScrollPrev && previous.canScrollNext === canScrollNext\n ? previous\n : { canScrollPrev, canScrollNext },\n );\n };\n\n update();\n list.addEventListener('scroll', update, { passive: true });\n\n // The list's own box stays the same width when a tab's label reflows or a\n // font finishes loading, so the tabs have to be observed as well as the\n // container - only their sizes tell us the strip started overflowing.\n const observer = new ResizeObserver(update);\n observer.observe(list);\n Array.from(list.children).forEach((child) => observer.observe(child));\n\n return () => {\n list.removeEventListener('scroll', update);\n observer.disconnect();\n };\n }, [hasScrollButtons, tabChildren.length, size, variant, fullWidth]);\n\n const scrollByStep = (direction: -1 | 1) => {\n const list = listRef.current;\n if (!list) return;\n\n // A step just short of a full page keeps a tab of context on screen, the\n // same way a paged scrollbar click does.\n const step = list.clientWidth * 0.8;\n const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n list.scrollBy({ left: direction * step, behavior: reducedMotion ? 'auto' : 'smooth' });\n };\n\n const onSelect = (newValue: string) => {\n if (!isControlled) {\n setLocalValue(newValue);\n }\n onChange?.(newValue);\n };\n\n const rootClasses = ['eidos-tabs', `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className]\n .filter(Boolean)\n .join(' ');\n\n const listClasses = [\n 'eidos-tabs-list',\n `eidos-tabs-list--${size}`,\n fullWidth && 'eidos-tabs-list--fullWidth',\n // With buttons, they are the affordance - a native scrollbar underneath\n // them is redundant, and on a space-taking one it would also sit over the\n // `line` variant's rule.\n hasScrollButtons && 'eidos-tabs-list--hideScrollbar',\n ]\n .filter(Boolean)\n .join(' ');\n\n // Each button exists only while its own direction has somewhere to go: none\n // at all when the strip fits, and none at the end you have reached. A\n // disabled button would be pointing at nothing. They are positioned\n // absolutely, so appearing and disappearing never moves the tabs.\n const showPrevButton = hasScrollButtons && scrollState.canScrollPrev;\n const showNextButton = hasScrollButtons && scrollState.canScrollNext;\n\n const scrollButtonClasses = (direction: 'prev' | 'next') =>\n [\n 'eidos-tabs-scroll-button',\n `eidos-tabs-scroll-button--${direction}`,\n `eidos-tabs-scroll-button--${size}`,\n ].join(' ');\n\n const contextValue: TabsContextValue = {\n activeValue,\n onSelect,\n variant,\n size,\n color,\n fullWidth,\n listRef,\n };\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={rootClasses}>\n {/* The buttons are siblings of the tab list, never children of it:\n `role=\"tablist\"` only accepts tabs. They are also hidden from\n assistive tech and skipped by Tab, because they are a pointer\n affordance for something the keyboard already does - arrow keys\n move between tabs and scroll the active one into view. */}\n <div className=\"eidos-tabs-bar\">\n {showPrevButton && (\n <button\n type=\"button\"\n className={scrollButtonClasses('prev')}\n onClick={() => scrollByStep(-1)}\n tabIndex={-1}\n aria-hidden=\"true\"\n >\n <ChevronLeft className=\"eidos-tabs-scroll-button-icon\" />\n </button>\n )}\n\n <div ref={listRef} className={listClasses} role=\"tablist\">\n {tabChildren}\n {variant === 'line' && (\n <span ref={indicatorRef} className=\"eidos-tabs-indicator\" aria-hidden=\"true\" />\n )}\n </div>\n\n {showNextButton && (\n <button\n type=\"button\"\n className={scrollButtonClasses('next')}\n onClick={() => scrollByStep(1)}\n tabIndex={-1}\n aria-hidden=\"true\"\n >\n <ChevronRight className=\"eidos-tabs-scroll-button-icon\" />\n </button>\n )}\n </div>\n\n <div className=\"eidos-tabs-panels\">{panelChildren}</div>\n </div>\n </TabsContext.Provider>\n );\n};\n\nTabs.displayName = 'Tabs';\n"],"mappings":";;;;;AAAA,OAAO,SAAS,UAAU,QAAQ,YAAY,WAAW,uBAAuB;AAChF,SAAS,aAAa,oBAAoB;AAkEtC,SAYW,KAZX;AA1DJ,IAAM,cAAc,MAAM,cAAuC,IAAI;AAErE,IAAM,iBAAiB,MAAwB;AAC7C,QAAM,MAAM,WAAW,WAAW;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,yCAAyC;AACnE,SAAO;AACT;AAOO,IAAM,MAA0B,CAAC;AAAA,EACtC;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,YAAY;AACd,MAAM;AACJ,QAAM,EAAE,aAAa,UAAU,MAAM,WAAW,QAAQ,IAAI,eAAe;AAC3E,QAAM,WAAW,gBAAgB;AAEjC,QAAM,gBAAgB,CAAC,MAA8C;AACnE,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAEX,UAAM,UAAU,MAAM;AAAA,MACpB,KAAK,iBAAiB,8BAA8B;AAAA,IACtD;AACA,UAAM,eAAe,QAAQ,QAAQ,EAAE,aAAa;AACpD,QAAI,iBAAiB,GAAI;AAEzB,QAAI,cAAc;AAClB,QAAI,EAAE,QAAQ,aAAc,gBAAe,eAAe,KAAK,QAAQ;AAAA,aAC9D,EAAE,QAAQ;AACjB,qBAAe,eAAe,IAAI,QAAQ,UAAU,QAAQ;AAAA,aACrD,EAAE,QAAQ,OAAQ,eAAc;AAAA,aAChC,EAAE,QAAQ,MAAO,eAAc,QAAQ,SAAS;AAEzD,QAAI,gBAAgB,IAAI;AACtB,QAAE,eAAe;AACjB,cAAQ,WAAW,EAAE,MAAM;AAAA,IAC7B;AAAA,EACF;AAEA,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,cAAc,IAAI;AAAA,IAClB,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,aAAa;AAAA,IACb;AAAA,EACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,aAAa,KAAK;AAAA,MACtB,MAAK;AAAA,MACL,iBAAe;AAAA,MACf,iBAAe,kBAAkB,KAAK;AAAA,MACtC,UAAU,WAAW,IAAI;AAAA,MACzB;AAAA,MACA,WAAW;AAAA,MACX,SAAS,MAAM,CAAC,YAAY,SAAS,KAAK;AAAA,MAC1C,WAAW;AAAA,MACX,MAAK;AAAA,MAEJ;AAAA,gBAAQ,oBAAC,UAAK,WAAU,kBAAkB,qBAAW,MAAM,oBAAoB,GAAE;AAAA,QAClF,oBAAC,UAAK,WAAU,mBAAmB,UAAS;AAAA;AAAA;AAAA,EAC9C;AAEJ;AAEA,IAAI,cAAc;AAOX,IAAM,WAAoC,CAAC,EAAE,OAAO,UAAU,YAAY,GAAG,MAAM;AACxF,QAAM,EAAE,YAAY,IAAI,eAAe;AACvC,QAAM,WAAW,gBAAgB;AAEjC,MAAI,CAAC,SAAU,QAAO;AAEtB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,kBAAkB,KAAK;AAAA,MAC3B,MAAK;AAAA,MACL,mBAAiB,aAAa,KAAK;AAAA,MACnC,UAAU;AAAA,MACV,WAAW,CAAC,mBAAmB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MAEjE;AAAA;AAAA,EACH;AAEJ;AAEA,SAAS,cAAc;AAMhB,IAAM,OAA4B,CAAC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ;AACF,MAAM;AACJ,QAAM,eAAe,UAAU;AAC/B,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,gBAAgB,EAAE;AAC/D,QAAM,cAAc,eAAe,QAAS;AAI5C,QAAM,cAAoC,CAAC;AAC3C,QAAM,gBAAsC,CAAC;AAE7C,QAAM,SAAS,QAAQ,UAAU,CAAC,UAAU;AAC1C,QAAI,CAAC,MAAM,eAAe,KAAK,EAAG;AAClC,QAAI,MAAM,SAAS,KAAK;AACtB,kBAAY,KAAK,KAAK;AAAA,IACxB,WAAW,MAAM,SAAS,UAAU;AAClC,oBAAc,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF,CAAC;AAED,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,eAAe,OAAwB,IAAI;AAIjD,QAAM,gBAAgB,OAAO,KAAK;AAElC,kBAAgB,MAAM;AACpB,QAAI,YAAY,OAAQ;AACxB,UAAM,OAAO,QAAQ;AACrB,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,QAAQ,CAAC,UAAW;AAEzB,UAAM,YAAY,KAAK,cAAc,wBAAwB;AAC7D,QAAI,CAAC,UAAW;AAIhB,QAAI,CAAC,cAAc,SAAS;AAC1B,gBAAU,MAAM,aAAa;AAAA,IAC/B;AAEA,cAAU,MAAM,OAAO,GAAG,UAAU,UAAU;AAC9C,cAAU,MAAM,QAAQ,GAAG,UAAU,WAAW;AAEhD,QAAI,CAAC,cAAc,SAAS;AAG1B,WAAK,UAAU;AACf,gBAAU,MAAM,aAAa;AAC7B,oBAAc,UAAU;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,aAAa,SAAS,SAAS,CAAC;AAQpC,kBAAgB,MAAM;AACpB,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAEX,UAAM,YAAY,KAAK,cAAc,wBAAwB;AAC7D,QAAI,CAAC,UAAW;AAEhB,UAAM,OAAO,UAAU;AACvB,UAAM,QAAQ,OAAO,UAAU;AAE/B,QAAI,OAAO,KAAK,YAAY;AAC1B,WAAK,aAAa;AAAA,IACpB,WAAW,QAAQ,KAAK,aAAa,KAAK,aAAa;AACrD,WAAK,aAAa,QAAQ,KAAK;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAKhB,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,EAAE,eAAe,OAAO,eAAe,MAAM,CAAC;AAC7F,QAAM,mBAAmB,kBAAkB;AAE3C,YAAU,MAAM;AACd,QAAI,CAAC,iBAAkB;AACvB,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAEX,UAAM,SAAS,MAAM;AACnB,YAAM,YAAY,KAAK,cAAc,KAAK;AAI1C,YAAM,gBAAgB,KAAK,aAAa;AACxC,YAAM,gBAAgB,KAAK,aAAa,YAAY;AACpD;AAAA,QAAe,CAAC,aACd,SAAS,kBAAkB,iBAAiB,SAAS,kBAAkB,gBACnE,WACA,EAAE,eAAe,cAAc;AAAA,MACrC;AAAA,IACF;AAEA,WAAO;AACP,SAAK,iBAAiB,UAAU,QAAQ,EAAE,SAAS,KAAK,CAAC;AAKzD,UAAM,WAAW,IAAI,eAAe,MAAM;AAC1C,aAAS,QAAQ,IAAI;AACrB,UAAM,KAAK,KAAK,QAAQ,EAAE,QAAQ,CAAC,UAAU,SAAS,QAAQ,KAAK,CAAC;AAEpE,WAAO,MAAM;AACX,WAAK,oBAAoB,UAAU,MAAM;AACzC,eAAS,WAAW;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,kBAAkB,YAAY,QAAQ,MAAM,SAAS,SAAS,CAAC;AAEnE,QAAM,eAAe,CAAC,cAAsB;AAC1C,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAIX,UAAM,OAAO,KAAK,cAAc;AAChC,UAAM,gBAAgB,OAAO,WAAW,kCAAkC,EAAE;AAC5E,SAAK,SAAS,EAAE,MAAM,YAAY,MAAM,UAAU,gBAAgB,SAAS,SAAS,CAAC;AAAA,EACvF;AAEA,QAAM,WAAW,CAAC,aAAqB;AACrC,QAAI,CAAC,cAAc;AACjB,oBAAc,QAAQ;AAAA,IACxB;AACA,eAAW,QAAQ;AAAA,EACrB;AAEA,QAAM,cAAc,CAAC,cAAc,eAAe,OAAO,IAAI,eAAe,KAAK,IAAI,SAAS,EAC3F,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,QAAM,cAAc;AAAA,IAClB;AAAA,IACA,oBAAoB,IAAI;AAAA,IACxB,aAAa;AAAA;AAAA;AAAA;AAAA,IAIb,oBAAoB;AAAA,EACtB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAMX,QAAM,iBAAiB,oBAAoB,YAAY;AACvD,QAAM,iBAAiB,oBAAoB,YAAY;AAEvD,QAAM,sBAAsB,CAAC,cAC3B;AAAA,IACE;AAAA,IACA,6BAA6B,SAAS;AAAA,IACtC,6BAA6B,IAAI;AAAA,EACnC,EAAE,KAAK,GAAG;AAEZ,QAAM,eAAiC;AAAA,IACrC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SACE,oBAAC,YAAY,UAAZ,EAAqB,OAAO,cAC3B,+BAAC,SAAI,WAAW,aAMd;AAAA,yBAAC,SAAI,WAAU,kBACZ;AAAA,wBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW,oBAAoB,MAAM;AAAA,UACrC,SAAS,MAAM,aAAa,EAAE;AAAA,UAC9B,UAAU;AAAA,UACV,eAAY;AAAA,UAEZ,8BAAC,eAAY,WAAU,iCAAgC;AAAA;AAAA,MACzD;AAAA,MAGF,qBAAC,SAAI,KAAK,SAAS,WAAW,aAAa,MAAK,WAC7C;AAAA;AAAA,QACA,YAAY,UACX,oBAAC,UAAK,KAAK,cAAc,WAAU,wBAAuB,eAAY,QAAO;AAAA,SAEjF;AAAA,MAEC,kBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW,oBAAoB,MAAM;AAAA,UACrC,SAAS,MAAM,aAAa,CAAC;AAAA,UAC7B,UAAU;AAAA,UACV,eAAY;AAAA,UAEZ,8BAAC,gBAAa,WAAU,iCAAgC;AAAA;AAAA,MAC1D;AAAA,OAEJ;AAAA,IAEA,oBAAC,SAAI,WAAU,qBAAqB,yBAAc;AAAA,KACpD,GACF;AAEJ;AAEA,KAAK,cAAc;","names":[]}
|
|
@@ -4,6 +4,7 @@ var _chunkGEHL4LKKcjs = require('./chunk-GEHL4LKK.cjs');
|
|
|
4
4
|
|
|
5
5
|
// src/components/Tabs/Tabs.component.tsx
|
|
6
6
|
var _react = require('react'); var _react2 = _interopRequireDefault(_react);
|
|
7
|
+
var _lucidereact = require('lucide-react');
|
|
7
8
|
var _jsxruntime = require('react/jsx-runtime');
|
|
8
9
|
var TabsContext = _react2.default.createContext(null);
|
|
9
10
|
var useTabsContext = () => {
|
|
@@ -93,12 +94,23 @@ var Tabs = ({
|
|
|
93
94
|
size = "md",
|
|
94
95
|
color = "primary",
|
|
95
96
|
fullWidth = false,
|
|
97
|
+
scrollButtons = "auto",
|
|
96
98
|
className = "",
|
|
97
99
|
children
|
|
98
100
|
}) => {
|
|
99
101
|
const isControlled = value !== void 0;
|
|
100
102
|
const [localValue, setLocalValue] = _react.useState.call(void 0, _nullishCoalesce(defaultValue, () => ( "")));
|
|
101
103
|
const activeValue = isControlled ? value : localValue;
|
|
104
|
+
const tabChildren = [];
|
|
105
|
+
const panelChildren = [];
|
|
106
|
+
_react2.default.Children.forEach(children, (child) => {
|
|
107
|
+
if (!_react2.default.isValidElement(child)) return;
|
|
108
|
+
if (child.type === Tab) {
|
|
109
|
+
tabChildren.push(child);
|
|
110
|
+
} else if (child.type === TabPanel) {
|
|
111
|
+
panelChildren.push(child);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
102
114
|
const listRef = _react.useRef.call(void 0, null);
|
|
103
115
|
const indicatorRef = _react.useRef.call(void 0, null);
|
|
104
116
|
const hasPositioned = _react.useRef.call(void 0, false);
|
|
@@ -120,28 +132,73 @@ var Tabs = ({
|
|
|
120
132
|
hasPositioned.current = true;
|
|
121
133
|
}
|
|
122
134
|
}, [activeValue, variant, fullWidth]);
|
|
135
|
+
_react.useLayoutEffect.call(void 0, () => {
|
|
136
|
+
const list = listRef.current;
|
|
137
|
+
if (!list) return;
|
|
138
|
+
const activeTab = list.querySelector('[aria-selected="true"]');
|
|
139
|
+
if (!activeTab) return;
|
|
140
|
+
const left = activeTab.offsetLeft;
|
|
141
|
+
const right = left + activeTab.offsetWidth;
|
|
142
|
+
if (left < list.scrollLeft) {
|
|
143
|
+
list.scrollLeft = left;
|
|
144
|
+
} else if (right > list.scrollLeft + list.clientWidth) {
|
|
145
|
+
list.scrollLeft = right - list.clientWidth;
|
|
146
|
+
}
|
|
147
|
+
}, [activeValue]);
|
|
148
|
+
const [scrollState, setScrollState] = _react.useState.call(void 0, { canScrollPrev: false, canScrollNext: false });
|
|
149
|
+
const hasScrollButtons = scrollButtons !== "none";
|
|
150
|
+
_react.useEffect.call(void 0, () => {
|
|
151
|
+
if (!hasScrollButtons) return;
|
|
152
|
+
const list = listRef.current;
|
|
153
|
+
if (!list) return;
|
|
154
|
+
const update = () => {
|
|
155
|
+
const maxScroll = list.scrollWidth - list.clientWidth;
|
|
156
|
+
const canScrollPrev = list.scrollLeft > 1;
|
|
157
|
+
const canScrollNext = list.scrollLeft < maxScroll - 1;
|
|
158
|
+
setScrollState(
|
|
159
|
+
(previous) => previous.canScrollPrev === canScrollPrev && previous.canScrollNext === canScrollNext ? previous : { canScrollPrev, canScrollNext }
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
update();
|
|
163
|
+
list.addEventListener("scroll", update, { passive: true });
|
|
164
|
+
const observer = new ResizeObserver(update);
|
|
165
|
+
observer.observe(list);
|
|
166
|
+
Array.from(list.children).forEach((child) => observer.observe(child));
|
|
167
|
+
return () => {
|
|
168
|
+
list.removeEventListener("scroll", update);
|
|
169
|
+
observer.disconnect();
|
|
170
|
+
};
|
|
171
|
+
}, [hasScrollButtons, tabChildren.length, size, variant, fullWidth]);
|
|
172
|
+
const scrollByStep = (direction) => {
|
|
173
|
+
const list = listRef.current;
|
|
174
|
+
if (!list) return;
|
|
175
|
+
const step = list.clientWidth * 0.8;
|
|
176
|
+
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
177
|
+
list.scrollBy({ left: direction * step, behavior: reducedMotion ? "auto" : "smooth" });
|
|
178
|
+
};
|
|
123
179
|
const onSelect = (newValue) => {
|
|
124
180
|
if (!isControlled) {
|
|
125
181
|
setLocalValue(newValue);
|
|
126
182
|
}
|
|
127
183
|
_optionalChain([onChange, 'optionalCall', _ => _(newValue)]);
|
|
128
184
|
};
|
|
129
|
-
const tabChildren = [];
|
|
130
|
-
const panelChildren = [];
|
|
131
|
-
_react2.default.Children.forEach(children, (child) => {
|
|
132
|
-
if (!_react2.default.isValidElement(child)) return;
|
|
133
|
-
if (child.type === Tab) {
|
|
134
|
-
tabChildren.push(child);
|
|
135
|
-
} else if (child.type === TabPanel) {
|
|
136
|
-
panelChildren.push(child);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
185
|
const rootClasses = ["eidos-tabs", `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className].filter(Boolean).join(" ");
|
|
140
186
|
const listClasses = [
|
|
141
187
|
"eidos-tabs-list",
|
|
142
188
|
`eidos-tabs-list--${size}`,
|
|
143
|
-
fullWidth && "eidos-tabs-list--fullWidth"
|
|
189
|
+
fullWidth && "eidos-tabs-list--fullWidth",
|
|
190
|
+
// With buttons, they are the affordance - a native scrollbar underneath
|
|
191
|
+
// them is redundant, and on a space-taking one it would also sit over the
|
|
192
|
+
// `line` variant's rule.
|
|
193
|
+
hasScrollButtons && "eidos-tabs-list--hideScrollbar"
|
|
144
194
|
].filter(Boolean).join(" ");
|
|
195
|
+
const showPrevButton = hasScrollButtons && scrollState.canScrollPrev;
|
|
196
|
+
const showNextButton = hasScrollButtons && scrollState.canScrollNext;
|
|
197
|
+
const scrollButtonClasses = (direction) => [
|
|
198
|
+
"eidos-tabs-scroll-button",
|
|
199
|
+
`eidos-tabs-scroll-button--${direction}`,
|
|
200
|
+
`eidos-tabs-scroll-button--${size}`
|
|
201
|
+
].join(" ");
|
|
145
202
|
const contextValue = {
|
|
146
203
|
activeValue,
|
|
147
204
|
onSelect,
|
|
@@ -152,9 +209,33 @@ var Tabs = ({
|
|
|
152
209
|
listRef
|
|
153
210
|
};
|
|
154
211
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TabsContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: rootClasses, children: [
|
|
155
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", {
|
|
156
|
-
|
|
157
|
-
|
|
212
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "eidos-tabs-bar", children: [
|
|
213
|
+
showPrevButton && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
214
|
+
"button",
|
|
215
|
+
{
|
|
216
|
+
type: "button",
|
|
217
|
+
className: scrollButtonClasses("prev"),
|
|
218
|
+
onClick: () => scrollByStep(-1),
|
|
219
|
+
tabIndex: -1,
|
|
220
|
+
"aria-hidden": "true",
|
|
221
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft, { className: "eidos-tabs-scroll-button-icon" })
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { ref: listRef, className: listClasses, role: "tablist", children: [
|
|
225
|
+
tabChildren,
|
|
226
|
+
variant === "line" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { ref: indicatorRef, className: "eidos-tabs-indicator", "aria-hidden": "true" })
|
|
227
|
+
] }),
|
|
228
|
+
showNextButton && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
229
|
+
"button",
|
|
230
|
+
{
|
|
231
|
+
type: "button",
|
|
232
|
+
className: scrollButtonClasses("next"),
|
|
233
|
+
onClick: () => scrollByStep(1),
|
|
234
|
+
tabIndex: -1,
|
|
235
|
+
"aria-hidden": "true",
|
|
236
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ChevronRight, { className: "eidos-tabs-scroll-button-icon" })
|
|
237
|
+
}
|
|
238
|
+
)
|
|
158
239
|
] }),
|
|
159
240
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-tabs-panels", children: panelChildren })
|
|
160
241
|
] }) });
|
|
@@ -166,4 +247,4 @@ Tabs.displayName = "Tabs";
|
|
|
166
247
|
|
|
167
248
|
|
|
168
249
|
exports.Tab = Tab; exports.TabPanel = TabPanel; exports.Tabs = Tabs;
|
|
169
|
-
//# sourceMappingURL=chunk-
|
|
250
|
+
//# sourceMappingURL=chunk-KQD7CQ4Z.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-KQD7CQ4Z.cjs","../src/components/Tabs/Tabs.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-KQD7CQ4Z.cjs","sourcesContent":[null,"import React, { useState, useRef, useContext, useEffect, useLayoutEffect } from 'react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { renderIcon } from '../../utils';\nimport type { TabsProps, TabProps, TabPanelProps, TabsContextValue } from './Tabs.types';\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null);\n\nconst useTabsContext = (): TabsContextValue => {\n const ctx = useContext(TabsContext);\n if (!ctx) throw new Error('Tab/TabPanel must be used inside <Tabs>');\n return ctx;\n};\n\n// ============================================================================\n// TAB - individual tab trigger button\n// Defined before Tabs so Tabs can use `child.type === Tab` for child separation.\n// ============================================================================\n\nexport const Tab: React.FC<TabProps> = ({\n value,\n children,\n disabled = false,\n icon,\n className = '',\n}) => {\n const { activeValue, onSelect, size, fullWidth, listRef } = useTabsContext();\n const isActive = activeValue === value;\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n const list = listRef.current;\n if (!list) return;\n\n const allTabs = Array.from(\n list.querySelectorAll('[role=\"tab\"]:not([disabled])'),\n ) as HTMLElement[];\n const currentIndex = allTabs.indexOf(e.currentTarget);\n if (currentIndex === -1) return;\n\n let targetIndex = -1;\n if (e.key === 'ArrowRight') targetIndex = (currentIndex + 1) % allTabs.length;\n else if (e.key === 'ArrowLeft')\n targetIndex = (currentIndex - 1 + allTabs.length) % allTabs.length;\n else if (e.key === 'Home') targetIndex = 0;\n else if (e.key === 'End') targetIndex = allTabs.length - 1;\n\n if (targetIndex !== -1) {\n e.preventDefault();\n allTabs[targetIndex].focus();\n }\n };\n\n const tabClasses = [\n 'eidos-tab',\n `eidos-tab--${size}`,\n isActive && 'eidos-tab--active',\n disabled && 'eidos-tab--disabled',\n fullWidth && 'eidos-tab--fullWidth',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <button\n id={`eidos-tab-${value}`}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`eidos-tabpanel-${value}`}\n tabIndex={isActive ? 0 : -1}\n disabled={disabled}\n className={tabClasses}\n onClick={() => !disabled && onSelect(value)}\n onKeyDown={handleKeyDown}\n type=\"button\"\n >\n {icon && <span className=\"eidos-tab-icon\">{renderIcon(icon, 'eidos-tab-icon-svg')}</span>}\n <span className=\"eidos-tab-label\">{children}</span>\n </button>\n );\n};\n\nTab.displayName = 'Tab';\n\n// ============================================================================\n// TAB PANEL - content region shown when its value matches the active tab\n// Defined before Tabs for the same `child.type === TabPanel` check.\n// ============================================================================\n\nexport const TabPanel: React.FC<TabPanelProps> = ({ value, children, className = '' }) => {\n const { activeValue } = useTabsContext();\n const isActive = activeValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n id={`eidos-tabpanel-${value}`}\n role=\"tabpanel\"\n aria-labelledby={`eidos-tab-${value}`}\n tabIndex={0}\n className={['eidos-tab-panel', className].filter(Boolean).join(' ')}\n >\n {children}\n </div>\n );\n};\n\nTabPanel.displayName = 'TabPanel';\n\n// ============================================================================\n// TABS - root container; provides context, separates tab/panel children\n// ============================================================================\n\nexport const Tabs: React.FC<TabsProps> = ({\n value,\n defaultValue,\n onChange,\n variant = 'line',\n size = 'md',\n color = 'primary',\n fullWidth = false,\n scrollButtons = 'auto',\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n const [localValue, setLocalValue] = useState(defaultValue ?? '');\n const activeValue = isControlled ? value! : localValue;\n\n // Separate Tab and TabPanel children so the list and panels render in\n // distinct DOM regions, regardless of how the consumer orders them.\n const tabChildren: React.ReactElement[] = [];\n const panelChildren: React.ReactElement[] = [];\n\n React.Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return;\n if (child.type === Tab) {\n tabChildren.push(child);\n } else if (child.type === TabPanel) {\n panelChildren.push(child);\n }\n });\n\n const listRef = useRef<HTMLDivElement>(null);\n const indicatorRef = useRef<HTMLSpanElement>(null);\n // Tracks whether the indicator has been positioned at least once so we can\n // skip the CSS transition on the very first render and avoid it animating\n // from the top-left corner to the initial tab.\n const hasPositioned = useRef(false);\n\n useLayoutEffect(() => {\n if (variant !== 'line') return;\n const list = listRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n // Suppress the CSS transition on the initial paint so the indicator snaps\n // into place rather than sliding from 0.\n if (!hasPositioned.current) {\n indicator.style.transition = 'none';\n }\n\n indicator.style.left = `${activeTab.offsetLeft}px`;\n indicator.style.width = `${activeTab.offsetWidth}px`;\n\n if (!hasPositioned.current) {\n // Force a synchronous reflow so the browser commits the no-transition\n // position before we re-enable the transition for future clicks.\n void indicator.offsetWidth;\n indicator.style.transition = '';\n hasPositioned.current = true;\n }\n }, [activeValue, variant, fullWidth]);\n\n // The list scrolls horizontally when the strip is wider than its container,\n // so an activated tab can sit outside the scrollport - reachable by keyboard\n // (`End`) or by a controlled `value` change, but invisible. `scrollLeft` is\n // adjusted directly rather than via `scrollIntoView`, which walks the whole\n // ancestor chain and would scroll the page vertically when the strip is below\n // the fold.\n useLayoutEffect(() => {\n const list = listRef.current;\n if (!list) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n const left = activeTab.offsetLeft;\n const right = left + activeTab.offsetWidth;\n\n if (left < list.scrollLeft) {\n list.scrollLeft = left;\n } else if (right > list.scrollLeft + list.clientWidth) {\n list.scrollLeft = right - list.clientWidth;\n }\n }, [activeValue]);\n\n // Whether either end of the strip has content hidden past it. Drives both\n // the presence of the scroll buttons (neither end scrollable = the strip\n // fits, so no buttons) and their disabled state at the extremes.\n const [scrollState, setScrollState] = useState({ canScrollPrev: false, canScrollNext: false });\n const hasScrollButtons = scrollButtons !== 'none';\n\n useEffect(() => {\n if (!hasScrollButtons) return;\n const list = listRef.current;\n if (!list) return;\n\n const update = () => {\n const maxScroll = list.scrollWidth - list.clientWidth;\n // 1px of tolerance: fractional layout widths leave a sub-pixel remainder\n // at either end that would otherwise keep a button enabled for a scroll\n // that can no longer move anything.\n const canScrollPrev = list.scrollLeft > 1;\n const canScrollNext = list.scrollLeft < maxScroll - 1;\n setScrollState((previous) =>\n previous.canScrollPrev === canScrollPrev && previous.canScrollNext === canScrollNext\n ? previous\n : { canScrollPrev, canScrollNext },\n );\n };\n\n update();\n list.addEventListener('scroll', update, { passive: true });\n\n // The list's own box stays the same width when a tab's label reflows or a\n // font finishes loading, so the tabs have to be observed as well as the\n // container - only their sizes tell us the strip started overflowing.\n const observer = new ResizeObserver(update);\n observer.observe(list);\n Array.from(list.children).forEach((child) => observer.observe(child));\n\n return () => {\n list.removeEventListener('scroll', update);\n observer.disconnect();\n };\n }, [hasScrollButtons, tabChildren.length, size, variant, fullWidth]);\n\n const scrollByStep = (direction: -1 | 1) => {\n const list = listRef.current;\n if (!list) return;\n\n // A step just short of a full page keeps a tab of context on screen, the\n // same way a paged scrollbar click does.\n const step = list.clientWidth * 0.8;\n const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n list.scrollBy({ left: direction * step, behavior: reducedMotion ? 'auto' : 'smooth' });\n };\n\n const onSelect = (newValue: string) => {\n if (!isControlled) {\n setLocalValue(newValue);\n }\n onChange?.(newValue);\n };\n\n const rootClasses = ['eidos-tabs', `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className]\n .filter(Boolean)\n .join(' ');\n\n const listClasses = [\n 'eidos-tabs-list',\n `eidos-tabs-list--${size}`,\n fullWidth && 'eidos-tabs-list--fullWidth',\n // With buttons, they are the affordance - a native scrollbar underneath\n // them is redundant, and on a space-taking one it would also sit over the\n // `line` variant's rule.\n hasScrollButtons && 'eidos-tabs-list--hideScrollbar',\n ]\n .filter(Boolean)\n .join(' ');\n\n // Each button exists only while its own direction has somewhere to go: none\n // at all when the strip fits, and none at the end you have reached. A\n // disabled button would be pointing at nothing. They are positioned\n // absolutely, so appearing and disappearing never moves the tabs.\n const showPrevButton = hasScrollButtons && scrollState.canScrollPrev;\n const showNextButton = hasScrollButtons && scrollState.canScrollNext;\n\n const scrollButtonClasses = (direction: 'prev' | 'next') =>\n [\n 'eidos-tabs-scroll-button',\n `eidos-tabs-scroll-button--${direction}`,\n `eidos-tabs-scroll-button--${size}`,\n ].join(' ');\n\n const contextValue: TabsContextValue = {\n activeValue,\n onSelect,\n variant,\n size,\n color,\n fullWidth,\n listRef,\n };\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={rootClasses}>\n {/* The buttons are siblings of the tab list, never children of it:\n `role=\"tablist\"` only accepts tabs. They are also hidden from\n assistive tech and skipped by Tab, because they are a pointer\n affordance for something the keyboard already does - arrow keys\n move between tabs and scroll the active one into view. */}\n <div className=\"eidos-tabs-bar\">\n {showPrevButton && (\n <button\n type=\"button\"\n className={scrollButtonClasses('prev')}\n onClick={() => scrollByStep(-1)}\n tabIndex={-1}\n aria-hidden=\"true\"\n >\n <ChevronLeft className=\"eidos-tabs-scroll-button-icon\" />\n </button>\n )}\n\n <div ref={listRef} className={listClasses} role=\"tablist\">\n {tabChildren}\n {variant === 'line' && (\n <span ref={indicatorRef} className=\"eidos-tabs-indicator\" aria-hidden=\"true\" />\n )}\n </div>\n\n {showNextButton && (\n <button\n type=\"button\"\n className={scrollButtonClasses('next')}\n onClick={() => scrollByStep(1)}\n tabIndex={-1}\n aria-hidden=\"true\"\n >\n <ChevronRight className=\"eidos-tabs-scroll-button-icon\" />\n </button>\n )}\n </div>\n\n <div className=\"eidos-tabs-panels\">{panelChildren}</div>\n </div>\n </TabsContext.Provider>\n );\n};\n\nTabs.displayName = 'Tabs';\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,7 @@ var _chunk6A2ZKVSTcjs = require('./chunk-6A2ZKVST.cjs');
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
var
|
|
11
|
+
var _chunkKQD7CQ4Zcjs = require('./chunk-KQD7CQ4Z.cjs');
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
var _chunk442U3W6Rcjs = require('./chunk-442U3W6R.cjs');
|
|
@@ -291,5 +291,5 @@ require('./chunk-DZUJEN5N.cjs');
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
|
|
294
|
-
exports.Accordion = _chunkSDEKANNLcjs.Accordion; exports.AccordionItem = _chunkSDEKANNLcjs.AccordionItem; exports.Alert = _chunkB5G5XVFJcjs.Alert; exports.Avatar = _chunkQ3GNKF74cjs.Avatar; exports.AvatarGroup = _chunkQ3GNKF74cjs.AvatarGroup; exports.Badge = _chunk3ETWFS2Ncjs.Badge; exports.Breadcrumb = _chunk5O73AALPcjs.Breadcrumb; exports.Button = _chunkAPK5OQRVcjs.Button; exports.ButtonGroup = _chunk2ROKOO22cjs.ButtonGroup; exports.Calendar = _chunk6HUHH6Y4cjs.Calendar; exports.Card = _chunkV5RS3JZNcjs.Card; exports.Checkbox = _chunkIJ4DXG7Dcjs.Checkbox; exports.Chip = _chunkTRLBYA5Gcjs.Chip; exports.ColorPicker = _chunk5BX4Q76Dcjs.ColorPicker; exports.Combobox = _chunkWUBQJVB7cjs.Combobox; exports.CommandPalette = _chunk3EPND5M2cjs.CommandPalette; exports.ContextMenu = _chunkLFWPQYWXcjs.ContextMenu; exports.DataGrid = _chunkSKTDGCWJcjs.DataGrid; exports.DatePicker = _chunk6HUHH6Y4cjs.DatePicker; exports.Divider = _chunk366R4GKRcjs.Divider; exports.Drawer = _chunkLUL6RZ4Mcjs.Drawer; exports.Dropdown = _chunkFRUOA2QUcjs.Dropdown; exports.DropdownProvider = _chunkFRUOA2QUcjs.DropdownProvider; exports.EmptyState = _chunkKV3V2CPHcjs.EmptyState; exports.FONT_ACCEPT = _chunkU5FHQ73Ucjs.FONT_ACCEPT; exports.FileUpload = _chunk3CXULV4Gcjs.FileUpload; exports.Footer = _chunkPTBBY4ZXcjs.Footer; exports.Header = _chunkZ4SLV6WGcjs.Header; exports.IconButton = _chunkAPK5OQRVcjs.IconButton; exports.InlineEdit = _chunkN2IGZIEXcjs.InlineEdit; exports.Input = _chunk5VM2256Wcjs.Input; exports.Kbd = _chunkKVIL4EP4cjs.Kbd; exports.Menu = _chunkDAXZRNNOcjs.Menu; exports.MenuPanel = _chunkDAXZRNNOcjs.MenuPanel; exports.Modal = _chunkD5IY2HUVcjs.Modal; exports.Navigation = _chunkGT22EGJPcjs.Navigation; exports.NumberInput = _chunkQF2RAUPBcjs.NumberInput; exports.OTPInput = _chunkFKXKBJUIcjs.OTPInput; exports.PageLayout = _chunkG77YR73Rcjs.PageLayout; exports.Pagination = _chunkAXBYJ2OYcjs.Pagination; exports.Pill = _chunkEQ6BNPTLcjs.Pill; exports.Popover = _chunk3E7TKBQIcjs.Popover; exports.Progress = _chunkV2S6XWIQcjs.Progress; exports.Radio = _chunkQQIALSTXcjs.Radio; exports.RadioGroup = _chunkQQIALSTXcjs.RadioGroup; exports.SegmentedControl = _chunkAAA6TR5Icjs.SegmentedControl; exports.Select = _chunkKJZUYC4Xcjs.Select; exports.Skeleton = _chunkIYWAZEFIcjs.Skeleton; exports.Slider = _chunkOZZHOEN5cjs.Slider; exports.SnackbarContainer = _chunkEZME5CLKcjs.SnackbarContainer; exports.SnackbarProvider = _chunkEZME5CLKcjs.SnackbarProvider; exports.Spinner = _chunkTROYIUDHcjs.Spinner; exports.SplitButton = _chunkSKEWTQJ7cjs.SplitButton; exports.Stepper = _chunkHFZ5FKRGcjs.Stepper; exports.Switch = _chunkIXZMKIV5cjs.Switch; exports.Tab =
|
|
294
|
+
exports.Accordion = _chunkSDEKANNLcjs.Accordion; exports.AccordionItem = _chunkSDEKANNLcjs.AccordionItem; exports.Alert = _chunkB5G5XVFJcjs.Alert; exports.Avatar = _chunkQ3GNKF74cjs.Avatar; exports.AvatarGroup = _chunkQ3GNKF74cjs.AvatarGroup; exports.Badge = _chunk3ETWFS2Ncjs.Badge; exports.Breadcrumb = _chunk5O73AALPcjs.Breadcrumb; exports.Button = _chunkAPK5OQRVcjs.Button; exports.ButtonGroup = _chunk2ROKOO22cjs.ButtonGroup; exports.Calendar = _chunk6HUHH6Y4cjs.Calendar; exports.Card = _chunkV5RS3JZNcjs.Card; exports.Checkbox = _chunkIJ4DXG7Dcjs.Checkbox; exports.Chip = _chunkTRLBYA5Gcjs.Chip; exports.ColorPicker = _chunk5BX4Q76Dcjs.ColorPicker; exports.Combobox = _chunkWUBQJVB7cjs.Combobox; exports.CommandPalette = _chunk3EPND5M2cjs.CommandPalette; exports.ContextMenu = _chunkLFWPQYWXcjs.ContextMenu; exports.DataGrid = _chunkSKTDGCWJcjs.DataGrid; exports.DatePicker = _chunk6HUHH6Y4cjs.DatePicker; exports.Divider = _chunk366R4GKRcjs.Divider; exports.Drawer = _chunkLUL6RZ4Mcjs.Drawer; exports.Dropdown = _chunkFRUOA2QUcjs.Dropdown; exports.DropdownProvider = _chunkFRUOA2QUcjs.DropdownProvider; exports.EmptyState = _chunkKV3V2CPHcjs.EmptyState; exports.FONT_ACCEPT = _chunkU5FHQ73Ucjs.FONT_ACCEPT; exports.FileUpload = _chunk3CXULV4Gcjs.FileUpload; exports.Footer = _chunkPTBBY4ZXcjs.Footer; exports.Header = _chunkZ4SLV6WGcjs.Header; exports.IconButton = _chunkAPK5OQRVcjs.IconButton; exports.InlineEdit = _chunkN2IGZIEXcjs.InlineEdit; exports.Input = _chunk5VM2256Wcjs.Input; exports.Kbd = _chunkKVIL4EP4cjs.Kbd; exports.Menu = _chunkDAXZRNNOcjs.Menu; exports.MenuPanel = _chunkDAXZRNNOcjs.MenuPanel; exports.Modal = _chunkD5IY2HUVcjs.Modal; exports.Navigation = _chunkGT22EGJPcjs.Navigation; exports.NumberInput = _chunkQF2RAUPBcjs.NumberInput; exports.OTPInput = _chunkFKXKBJUIcjs.OTPInput; exports.PageLayout = _chunkG77YR73Rcjs.PageLayout; exports.Pagination = _chunkAXBYJ2OYcjs.Pagination; exports.Pill = _chunkEQ6BNPTLcjs.Pill; exports.Popover = _chunk3E7TKBQIcjs.Popover; exports.Progress = _chunkV2S6XWIQcjs.Progress; exports.Radio = _chunkQQIALSTXcjs.Radio; exports.RadioGroup = _chunkQQIALSTXcjs.RadioGroup; exports.SegmentedControl = _chunkAAA6TR5Icjs.SegmentedControl; exports.Select = _chunkKJZUYC4Xcjs.Select; exports.Skeleton = _chunkIYWAZEFIcjs.Skeleton; exports.Slider = _chunkOZZHOEN5cjs.Slider; exports.SnackbarContainer = _chunkEZME5CLKcjs.SnackbarContainer; exports.SnackbarProvider = _chunkEZME5CLKcjs.SnackbarProvider; exports.Spinner = _chunkTROYIUDHcjs.Spinner; exports.SplitButton = _chunkSKEWTQJ7cjs.SplitButton; exports.Stepper = _chunkHFZ5FKRGcjs.Stepper; exports.Switch = _chunkIXZMKIV5cjs.Switch; exports.Tab = _chunkKQD7CQ4Zcjs.Tab; exports.TabPanel = _chunkKQD7CQ4Zcjs.TabPanel; exports.Table = _chunkGGSII6Y2cjs.Table; exports.TableFiltersDropdown = _chunkLIFUHQH5cjs.TableFiltersDropdown; exports.Tabs = _chunkKQD7CQ4Zcjs.Tabs; exports.TagInput = _chunk442U3W6Rcjs.TagInput; exports.Textarea = _chunkRHOT7AEIcjs.Textarea; exports.ThemeEditor = _chunkSE7MWOC7cjs.ThemeEditor; exports.ThemeProvider = _chunkU5FHQ73Ucjs.ThemeProvider; exports.TimeInput = _chunk6HUHH6Y4cjs.TimeInput; exports.Timeline = _chunkZMDZGL5Acjs.Timeline; exports.Toolbar = _chunkGR2PMCVNcjs.Toolbar; exports.Tooltip = _chunkZ5U4NJTWcjs.Tooltip; exports.TreeView = _chunkXKQMEVODcjs.TreeView; exports.VirtualList = _chunk6A2ZKVSTcjs.VirtualList; exports.defaultTheme = _chunkU5FHQ73Ucjs.defaultTheme; exports.familyNameFromFile = _chunkU5FHQ73Ucjs.familyNameFromFile; exports.isFontAvailable = _chunkU5FHQ73Ucjs.isFontAvailable; exports.isFontStackAvailable = _chunkU5FHQ73Ucjs.isFontStackAvailable; exports.registerFontFace = _chunkU5FHQ73Ucjs.registerFontFace; exports.registerFontFile = _chunkU5FHQ73Ucjs.registerFontFile; exports.renderIcon = _chunkGEHL4LKKcjs.renderIcon; exports.toFontStack = _chunkU5FHQ73Ucjs.toFontStack; exports.useDropdownContext = _chunkFRUOA2QUcjs.useDropdownContext; exports.useSnackbar = _chunkEZME5CLKcjs.useSnackbar; exports.useSnackbarContext = _chunkEZME5CLKcjs.useSnackbarContext; exports.useTheme = _chunkU5FHQ73Ucjs.useTheme;
|
|
295
295
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.css
CHANGED
|
@@ -113,6 +113,8 @@
|
|
|
113
113
|
--navigation-collapsed-width: 64px;
|
|
114
114
|
--footer-height: 60px;
|
|
115
115
|
--badge-offset: 0.3em;
|
|
116
|
+
--scrollbar-size: 10px;
|
|
117
|
+
--scrollbar-thumb: var(--primary-color);
|
|
116
118
|
--z-index-drawer: 1200;
|
|
117
119
|
--z-index-modal: 1300;
|
|
118
120
|
--z-index-dropdown: 1400;
|
|
@@ -134,6 +136,11 @@
|
|
|
134
136
|
|
|
135
137
|
html,
|
|
136
138
|
body {
|
|
139
|
+
/* `100%`, not `100vh`: on mobile `100vh` resolves against the *large*
|
|
140
|
+
viewport, so a full-height app shell ends up taller than what is actually
|
|
141
|
+
visible and its bottom chrome hides behind the browser UI. `height: 100%`
|
|
142
|
+
on `html` already resolves against the viewport, and on `body` against
|
|
143
|
+
`html`, so this is the same thing on desktop without that bug. */
|
|
137
144
|
height: 100%;
|
|
138
145
|
font-family: var(--font-family-primary);
|
|
139
146
|
-webkit-font-smoothing: antialiased;
|
|
@@ -141,7 +148,6 @@ body {
|
|
|
141
148
|
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
|
142
149
|
font-size: 14px;
|
|
143
150
|
line-height: 1.4;
|
|
144
|
-
margin-bottom: var(--spacing-sm);
|
|
145
151
|
color: var(--dark-color);
|
|
146
152
|
}
|
|
147
153
|
|
|
@@ -224,6 +230,44 @@ svg.lucide {
|
|
|
224
230
|
height: 1em;
|
|
225
231
|
}
|
|
226
232
|
|
|
233
|
+
/* Scrollbars, WebKit/Blink (Chrome, Brave, Edge, Safari).
|
|
234
|
+
Both dimensions are needed: `width` is the thickness of a *vertical* bar
|
|
235
|
+
only, so setting it alone left every horizontal scroller (`pre`, the
|
|
236
|
+
Table/DataGrid wrappers, Tabs' strip) at the OS default thickness.
|
|
237
|
+
Overridable through the `--scrollbar-*` tokens - see `variables.scss`. */
|
|
238
|
+
*::-webkit-scrollbar {
|
|
239
|
+
width: var(--scrollbar-size);
|
|
240
|
+
height: var(--scrollbar-size);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
*::-webkit-scrollbar-track {
|
|
244
|
+
background: transparent;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
*::-webkit-scrollbar-thumb {
|
|
248
|
+
background-color: var(--scrollbar-thumb);
|
|
249
|
+
/* A scrollbar thumb takes no `padding`, so a transparent border plus
|
|
250
|
+
`background-clip: padding-box` is what insets the visible bar from the
|
|
251
|
+
track - the clip does nothing on its own. The thumb still hit-tests over
|
|
252
|
+
its whole border box, so this keeps a slim look without shrinking the
|
|
253
|
+
target to something un-grabbable. */
|
|
254
|
+
border: 3px solid transparent;
|
|
255
|
+
background-clip: padding-box;
|
|
256
|
+
border-radius: var(--scrollbar-size);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* Firefox, which supports neither the pseudo-elements above nor an inset
|
|
260
|
+
thumb - `scrollbar-width`/`scrollbar-color` is its ceiling. Gated behind a
|
|
261
|
+
Firefox-only `@supports` test on purpose: the standard properties take
|
|
262
|
+
precedence over the pseudo-elements wherever both are supported, so
|
|
263
|
+
applying them unconditionally would replace the styling above in Chromium
|
|
264
|
+
with this coarser version. */
|
|
265
|
+
@supports (scrollbar-width: auto) and (-moz-appearance: none) {
|
|
266
|
+
* {
|
|
267
|
+
scrollbar-width: thin;
|
|
268
|
+
scrollbar-color: var(--scrollbar-thumb) transparent;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
227
271
|
.eidos-accordion {
|
|
228
272
|
display: flex;
|
|
229
273
|
flex-direction: column;
|
|
@@ -1307,6 +1351,7 @@ svg.lucide {
|
|
|
1307
1351
|
align-items: center;
|
|
1308
1352
|
cursor: pointer;
|
|
1309
1353
|
width: fit-content;
|
|
1354
|
+
position: relative;
|
|
1310
1355
|
}
|
|
1311
1356
|
.eidos-checkbox-wrapper--disabled {
|
|
1312
1357
|
cursor: not-allowed;
|
|
@@ -5073,6 +5118,7 @@ kbd.eidos-cmd-kbd {
|
|
|
5073
5118
|
display: inline-flex;
|
|
5074
5119
|
flex-direction: column;
|
|
5075
5120
|
gap: var(--spacing-xs);
|
|
5121
|
+
position: relative;
|
|
5076
5122
|
}
|
|
5077
5123
|
.eidos-number-input--fullWidth {
|
|
5078
5124
|
width: 100%;
|
|
@@ -5698,6 +5744,7 @@ kbd.eidos-cmd-kbd {
|
|
|
5698
5744
|
align-items: center;
|
|
5699
5745
|
cursor: pointer;
|
|
5700
5746
|
width: fit-content;
|
|
5747
|
+
position: relative;
|
|
5701
5748
|
}
|
|
5702
5749
|
.eidos-radio-wrapper--disabled {
|
|
5703
5750
|
cursor: not-allowed;
|
|
@@ -6903,6 +6950,7 @@ kbd.eidos-cmd-kbd {
|
|
|
6903
6950
|
align-items: center;
|
|
6904
6951
|
gap: var(--spacing-sm);
|
|
6905
6952
|
cursor: pointer;
|
|
6953
|
+
position: relative;
|
|
6906
6954
|
}
|
|
6907
6955
|
.eidos-switch-wrapper--disabled {
|
|
6908
6956
|
cursor: not-allowed;
|
|
@@ -7804,13 +7852,21 @@ kbd.eidos-cmd-kbd {
|
|
|
7804
7852
|
flex-direction: column;
|
|
7805
7853
|
}
|
|
7806
7854
|
.eidos-tabs--line .eidos-tabs-list {
|
|
7807
|
-
|
|
7855
|
+
padding-bottom: 2px;
|
|
7856
|
+
box-shadow: inset 0 -2px 0 var(--gray-200);
|
|
7808
7857
|
position: relative;
|
|
7809
7858
|
gap: 0;
|
|
7810
7859
|
}
|
|
7811
7860
|
.eidos-tabs--line .eidos-tab {
|
|
7812
7861
|
border-radius: 0;
|
|
7813
7862
|
}
|
|
7863
|
+
.eidos-tabs--enclosed .eidos-tabs-bar {
|
|
7864
|
+
background: var(--gray-100);
|
|
7865
|
+
border-radius: var(--border-radius-md);
|
|
7866
|
+
}
|
|
7867
|
+
.eidos-tabs--enclosed .eidos-tabs-scroll-button:hover {
|
|
7868
|
+
background: var(--gray-200);
|
|
7869
|
+
}
|
|
7814
7870
|
.eidos-tabs--enclosed .eidos-tabs-list {
|
|
7815
7871
|
background: var(--gray-100);
|
|
7816
7872
|
border-radius: var(--border-radius-md);
|
|
@@ -7828,6 +7884,9 @@ kbd.eidos-cmd-kbd {
|
|
|
7828
7884
|
gap: var(--spacing-xs);
|
|
7829
7885
|
}
|
|
7830
7886
|
.eidos-tabs--pills .eidos-tab {
|
|
7887
|
+
height: auto;
|
|
7888
|
+
padding-block: var(--spacing-sm);
|
|
7889
|
+
min-height: 24px;
|
|
7831
7890
|
border-radius: 9999px;
|
|
7832
7891
|
}
|
|
7833
7892
|
.eidos-tabs--pills .eidos-tab--active {
|
|
@@ -7835,15 +7894,89 @@ kbd.eidos-cmd-kbd {
|
|
|
7835
7894
|
color: var(--primary-contrast);
|
|
7836
7895
|
}
|
|
7837
7896
|
|
|
7897
|
+
.eidos-tabs-bar {
|
|
7898
|
+
display: flex;
|
|
7899
|
+
align-items: stretch;
|
|
7900
|
+
min-width: 0;
|
|
7901
|
+
position: relative;
|
|
7902
|
+
}
|
|
7903
|
+
|
|
7904
|
+
.eidos-tabs-scroll-button {
|
|
7905
|
+
background: none;
|
|
7906
|
+
border: none;
|
|
7907
|
+
padding: 0;
|
|
7908
|
+
margin: 0;
|
|
7909
|
+
font: inherit;
|
|
7910
|
+
cursor: pointer;
|
|
7911
|
+
}
|
|
7912
|
+
.eidos-tabs-scroll-button:focus {
|
|
7913
|
+
outline: none;
|
|
7914
|
+
}
|
|
7915
|
+
.eidos-tabs-scroll-button {
|
|
7916
|
+
display: flex;
|
|
7917
|
+
align-items: center;
|
|
7918
|
+
justify-content: center;
|
|
7919
|
+
position: absolute;
|
|
7920
|
+
top: 50%;
|
|
7921
|
+
transform: translateY(-50%);
|
|
7922
|
+
z-index: 1;
|
|
7923
|
+
padding: var(--spacing-sm);
|
|
7924
|
+
background: rgb(var(--white-rgb), 0.9);
|
|
7925
|
+
border: 1px solid var(--gray-200);
|
|
7926
|
+
border-radius: var(--border-radius-sm);
|
|
7927
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
|
|
7928
|
+
color: var(--gray-500);
|
|
7929
|
+
transition: color 0.2s ease, background-color 0.2s ease;
|
|
7930
|
+
}
|
|
7931
|
+
.eidos-tabs-scroll-button--prev {
|
|
7932
|
+
left: 0;
|
|
7933
|
+
}
|
|
7934
|
+
.eidos-tabs-scroll-button--next {
|
|
7935
|
+
right: 0;
|
|
7936
|
+
}
|
|
7937
|
+
.eidos-tabs-scroll-button:hover {
|
|
7938
|
+
color: var(--gray-700);
|
|
7939
|
+
background: var(--gray-100);
|
|
7940
|
+
}
|
|
7941
|
+
.eidos-tabs-scroll-button--sm {
|
|
7942
|
+
font-size: var(--font-size-xs);
|
|
7943
|
+
}
|
|
7944
|
+
.eidos-tabs-scroll-button--md {
|
|
7945
|
+
font-size: var(--font-size-sm);
|
|
7946
|
+
}
|
|
7947
|
+
.eidos-tabs-scroll-button--lg {
|
|
7948
|
+
font-size: var(--font-size-base);
|
|
7949
|
+
}
|
|
7950
|
+
|
|
7951
|
+
.eidos-tabs-scroll-button-icon {
|
|
7952
|
+
width: 1.25em;
|
|
7953
|
+
height: 1.25em;
|
|
7954
|
+
}
|
|
7955
|
+
|
|
7838
7956
|
.eidos-tabs-list {
|
|
7839
7957
|
display: flex;
|
|
7840
7958
|
align-items: center;
|
|
7841
7959
|
overflow-x: auto;
|
|
7842
7960
|
min-width: 0;
|
|
7843
7961
|
}
|
|
7962
|
+
.eidos-tabs-list--sm {
|
|
7963
|
+
height: var(--component-size-sm);
|
|
7964
|
+
}
|
|
7965
|
+
.eidos-tabs-list--md {
|
|
7966
|
+
height: var(--component-size-md);
|
|
7967
|
+
}
|
|
7968
|
+
.eidos-tabs-list--lg {
|
|
7969
|
+
height: var(--component-size-lg);
|
|
7970
|
+
}
|
|
7844
7971
|
.eidos-tabs-list--fullWidth .eidos-tab {
|
|
7845
7972
|
flex: 1;
|
|
7846
7973
|
}
|
|
7974
|
+
.eidos-tabs-list--hideScrollbar {
|
|
7975
|
+
scrollbar-width: none;
|
|
7976
|
+
}
|
|
7977
|
+
.eidos-tabs-list--hideScrollbar::-webkit-scrollbar {
|
|
7978
|
+
display: none;
|
|
7979
|
+
}
|
|
7847
7980
|
|
|
7848
7981
|
.eidos-tab {
|
|
7849
7982
|
background: none;
|
|
@@ -7864,24 +7997,22 @@ kbd.eidos-cmd-kbd {
|
|
|
7864
7997
|
white-space: nowrap;
|
|
7865
7998
|
color: var(--gray-500);
|
|
7866
7999
|
font-weight: var(--font-weight-medium);
|
|
8000
|
+
line-height: 1;
|
|
7867
8001
|
transition: all 0.2s ease;
|
|
7868
8002
|
border-radius: 0;
|
|
7869
|
-
|
|
8003
|
+
height: 100%;
|
|
7870
8004
|
}
|
|
7871
8005
|
.eidos-tab--sm {
|
|
7872
8006
|
font-size: var(--font-size-xs);
|
|
7873
|
-
padding: var(--spacing-
|
|
7874
|
-
height: var(--component-size-sm);
|
|
8007
|
+
padding-inline: var(--spacing-sm);
|
|
7875
8008
|
}
|
|
7876
8009
|
.eidos-tab--md {
|
|
7877
8010
|
font-size: var(--font-size-sm);
|
|
7878
|
-
padding: var(--spacing-
|
|
7879
|
-
height: var(--component-size-md);
|
|
8011
|
+
padding-inline: var(--spacing-md);
|
|
7880
8012
|
}
|
|
7881
8013
|
.eidos-tab--lg {
|
|
7882
8014
|
font-size: var(--font-size-base);
|
|
7883
|
-
padding: var(--spacing-
|
|
7884
|
-
height: var(--component-size-lg);
|
|
8015
|
+
padding-inline: var(--spacing-md);
|
|
7885
8016
|
}
|
|
7886
8017
|
.eidos-tab:hover:not(.eidos-tab--disabled):not(.eidos-tab--active) {
|
|
7887
8018
|
color: var(--gray-700);
|
|
@@ -7912,7 +8043,7 @@ kbd.eidos-cmd-kbd {
|
|
|
7912
8043
|
color: var(--primary-contrast);
|
|
7913
8044
|
}
|
|
7914
8045
|
.eidos-tabs--primary .eidos-tab:focus-visible {
|
|
7915
|
-
box-shadow: 0 0 0
|
|
8046
|
+
box-shadow: inset 0 0 0 2px rgba(var(--primary-rgb), 0.5);
|
|
7916
8047
|
}
|
|
7917
8048
|
|
|
7918
8049
|
.eidos-tabs--secondary .eidos-tab--active {
|
|
@@ -7926,7 +8057,7 @@ kbd.eidos-cmd-kbd {
|
|
|
7926
8057
|
color: var(--secondary-contrast);
|
|
7927
8058
|
}
|
|
7928
8059
|
.eidos-tabs--secondary .eidos-tab:focus-visible {
|
|
7929
|
-
box-shadow: 0 0 0
|
|
8060
|
+
box-shadow: inset 0 0 0 2px rgba(var(--secondary-rgb), 0.5);
|
|
7930
8061
|
}
|
|
7931
8062
|
|
|
7932
8063
|
.eidos-tabs--success .eidos-tab--active {
|
|
@@ -7940,7 +8071,7 @@ kbd.eidos-cmd-kbd {
|
|
|
7940
8071
|
color: var(--success-contrast);
|
|
7941
8072
|
}
|
|
7942
8073
|
.eidos-tabs--success .eidos-tab:focus-visible {
|
|
7943
|
-
box-shadow: 0 0 0
|
|
8074
|
+
box-shadow: inset 0 0 0 2px rgba(var(--success-rgb), 0.5);
|
|
7944
8075
|
}
|
|
7945
8076
|
|
|
7946
8077
|
.eidos-tabs--danger .eidos-tab--active {
|
|
@@ -7954,7 +8085,7 @@ kbd.eidos-cmd-kbd {
|
|
|
7954
8085
|
color: var(--danger-contrast);
|
|
7955
8086
|
}
|
|
7956
8087
|
.eidos-tabs--danger .eidos-tab:focus-visible {
|
|
7957
|
-
box-shadow: 0 0 0
|
|
8088
|
+
box-shadow: inset 0 0 0 2px rgba(var(--danger-rgb), 0.5);
|
|
7958
8089
|
}
|
|
7959
8090
|
|
|
7960
8091
|
.eidos-tabs--warning .eidos-tab--active {
|
|
@@ -7968,7 +8099,7 @@ kbd.eidos-cmd-kbd {
|
|
|
7968
8099
|
color: var(--warning-contrast);
|
|
7969
8100
|
}
|
|
7970
8101
|
.eidos-tabs--warning .eidos-tab:focus-visible {
|
|
7971
|
-
box-shadow: 0 0 0
|
|
8102
|
+
box-shadow: inset 0 0 0 2px rgba(var(--warning-rgb), 0.5);
|
|
7972
8103
|
}
|
|
7973
8104
|
|
|
7974
8105
|
.eidos-tabs--info .eidos-tab--active {
|
|
@@ -7982,12 +8113,16 @@ kbd.eidos-cmd-kbd {
|
|
|
7982
8113
|
color: var(--info-contrast);
|
|
7983
8114
|
}
|
|
7984
8115
|
.eidos-tabs--info .eidos-tab:focus-visible {
|
|
7985
|
-
box-shadow: 0 0 0
|
|
8116
|
+
box-shadow: inset 0 0 0 2px rgba(var(--info-rgb), 0.5);
|
|
8117
|
+
}
|
|
8118
|
+
|
|
8119
|
+
.eidos-tabs--pills .eidos-tab--active:focus-visible {
|
|
8120
|
+
box-shadow: inset 0 0 0 2px currentColor;
|
|
7986
8121
|
}
|
|
7987
8122
|
|
|
7988
8123
|
.eidos-tabs-indicator {
|
|
7989
8124
|
position: absolute;
|
|
7990
|
-
bottom:
|
|
8125
|
+
bottom: 0;
|
|
7991
8126
|
left: 0;
|
|
7992
8127
|
width: 0;
|
|
7993
8128
|
height: 2px;
|
|
@@ -8611,6 +8746,7 @@ kbd.eidos-cmd-kbd {
|
|
|
8611
8746
|
align-items: center;
|
|
8612
8747
|
gap: var(--spacing-xs);
|
|
8613
8748
|
flex: 0 0 auto;
|
|
8749
|
+
position: relative;
|
|
8614
8750
|
margin-left: auto;
|
|
8615
8751
|
}
|
|
8616
8752
|
.eidos-theme-editor-row--wide > .eidos-theme-editor-row__control {
|
|
@@ -9048,16 +9184,18 @@ kbd.eidos-cmd-kbd {
|
|
|
9048
9184
|
}
|
|
9049
9185
|
.eidos-pagelayout__content {
|
|
9050
9186
|
grid-area: content;
|
|
9051
|
-
display:
|
|
9052
|
-
|
|
9187
|
+
display: flex;
|
|
9188
|
+
flex-direction: column;
|
|
9053
9189
|
background-color: var(--gray-50);
|
|
9054
9190
|
min-height: 0;
|
|
9055
9191
|
min-width: 0;
|
|
9192
|
+
overflow: auto;
|
|
9193
|
+
}
|
|
9194
|
+
.eidos-pagelayout__header, .eidos-pagelayout__footer {
|
|
9195
|
+
flex: 0 0 auto;
|
|
9056
9196
|
}
|
|
9057
9197
|
.eidos-pagelayout__body {
|
|
9058
|
-
|
|
9059
|
-
min-height: 0;
|
|
9060
|
-
min-width: 0;
|
|
9198
|
+
flex: 1 0 auto;
|
|
9061
9199
|
}
|
|
9062
9200
|
.eidos-pagelayout__footer.eidos-footer--copyright {
|
|
9063
9201
|
padding: var(--spacing-md) var(--spacing-xl);
|
package/dist/index.js
CHANGED
package/dist/tabs/index.cjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkKQD7CQ4Zcjs = require('../chunk-KQD7CQ4Z.cjs');
|
|
6
6
|
require('../chunk-GEHL4LKK.cjs');
|
|
7
7
|
require('../chunk-DZUJEN5N.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
exports.Tab =
|
|
12
|
+
exports.Tab = _chunkKQD7CQ4Zcjs.Tab; exports.TabPanel = _chunkKQD7CQ4Zcjs.TabPanel; exports.Tabs = _chunkKQD7CQ4Zcjs.Tabs;
|
|
13
13
|
//# sourceMappingURL=index.cjs.map
|
package/dist/tabs/index.d.cts
CHANGED
|
@@ -3,6 +3,16 @@ import { I as IconType } from '../renderIcon-C2lEMMzb.cjs';
|
|
|
3
3
|
import { C as ComponentSizeProps } from '../sizes-DM6Rrrgq.cjs';
|
|
4
4
|
|
|
5
5
|
type TabsColorProps = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
|
|
6
|
+
/**
|
|
7
|
+
* How an overflowing tab strip is scrolled.
|
|
8
|
+
*
|
|
9
|
+
* - `auto` - previous/next buttons appear only while the strip overflows, and
|
|
10
|
+
* the native scrollbar is hidden because the buttons are the affordance.
|
|
11
|
+
* - `none` - no buttons; the strip keeps its native scrollbar.
|
|
12
|
+
*
|
|
13
|
+
* Native scrolling (touch swipe, trackpad, shift+wheel) works either way.
|
|
14
|
+
*/
|
|
15
|
+
type TabsScrollButtonsProps = 'auto' | 'none';
|
|
6
16
|
interface TabsProps {
|
|
7
17
|
value?: string;
|
|
8
18
|
defaultValue?: string;
|
|
@@ -11,6 +21,7 @@ interface TabsProps {
|
|
|
11
21
|
size?: ComponentSizeProps;
|
|
12
22
|
color?: TabsColorProps;
|
|
13
23
|
fullWidth?: boolean;
|
|
24
|
+
scrollButtons?: TabsScrollButtonsProps;
|
|
14
25
|
className?: string;
|
|
15
26
|
children: React__default.ReactNode;
|
|
16
27
|
}
|
package/dist/tabs/index.d.ts
CHANGED
|
@@ -3,6 +3,16 @@ import { I as IconType } from '../renderIcon-C2lEMMzb.js';
|
|
|
3
3
|
import { C as ComponentSizeProps } from '../sizes-DM6Rrrgq.js';
|
|
4
4
|
|
|
5
5
|
type TabsColorProps = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
|
|
6
|
+
/**
|
|
7
|
+
* How an overflowing tab strip is scrolled.
|
|
8
|
+
*
|
|
9
|
+
* - `auto` - previous/next buttons appear only while the strip overflows, and
|
|
10
|
+
* the native scrollbar is hidden because the buttons are the affordance.
|
|
11
|
+
* - `none` - no buttons; the strip keeps its native scrollbar.
|
|
12
|
+
*
|
|
13
|
+
* Native scrolling (touch swipe, trackpad, shift+wheel) works either way.
|
|
14
|
+
*/
|
|
15
|
+
type TabsScrollButtonsProps = 'auto' | 'none';
|
|
6
16
|
interface TabsProps {
|
|
7
17
|
value?: string;
|
|
8
18
|
defaultValue?: string;
|
|
@@ -11,6 +21,7 @@ interface TabsProps {
|
|
|
11
21
|
size?: ComponentSizeProps;
|
|
12
22
|
color?: TabsColorProps;
|
|
13
23
|
fullWidth?: boolean;
|
|
24
|
+
scrollButtons?: TabsScrollButtonsProps;
|
|
14
25
|
className?: string;
|
|
15
26
|
children: React__default.ReactNode;
|
|
16
27
|
}
|
package/dist/tabs/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/Tabs/Tabs.component.tsx"],"sourcesContent":["import React, { useState, useRef, useContext, useLayoutEffect } from 'react';\nimport { renderIcon } from '../../utils';\nimport type { TabsProps, TabProps, TabPanelProps, TabsContextValue } from './Tabs.types';\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null);\n\nconst useTabsContext = (): TabsContextValue => {\n const ctx = useContext(TabsContext);\n if (!ctx) throw new Error('Tab/TabPanel must be used inside <Tabs>');\n return ctx;\n};\n\n// ============================================================================\n// TAB - individual tab trigger button\n// Defined before Tabs so Tabs can use `child.type === Tab` for child separation.\n// ============================================================================\n\nexport const Tab: React.FC<TabProps> = ({\n value,\n children,\n disabled = false,\n icon,\n className = '',\n}) => {\n const { activeValue, onSelect, size, fullWidth, listRef } = useTabsContext();\n const isActive = activeValue === value;\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n const list = listRef.current;\n if (!list) return;\n\n const allTabs = Array.from(\n list.querySelectorAll('[role=\"tab\"]:not([disabled])'),\n ) as HTMLElement[];\n const currentIndex = allTabs.indexOf(e.currentTarget);\n if (currentIndex === -1) return;\n\n let targetIndex = -1;\n if (e.key === 'ArrowRight') targetIndex = (currentIndex + 1) % allTabs.length;\n else if (e.key === 'ArrowLeft')\n targetIndex = (currentIndex - 1 + allTabs.length) % allTabs.length;\n else if (e.key === 'Home') targetIndex = 0;\n else if (e.key === 'End') targetIndex = allTabs.length - 1;\n\n if (targetIndex !== -1) {\n e.preventDefault();\n allTabs[targetIndex].focus();\n }\n };\n\n const tabClasses = [\n 'eidos-tab',\n `eidos-tab--${size}`,\n isActive && 'eidos-tab--active',\n disabled && 'eidos-tab--disabled',\n fullWidth && 'eidos-tab--fullWidth',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <button\n id={`eidos-tab-${value}`}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`eidos-tabpanel-${value}`}\n tabIndex={isActive ? 0 : -1}\n disabled={disabled}\n className={tabClasses}\n onClick={() => !disabled && onSelect(value)}\n onKeyDown={handleKeyDown}\n type=\"button\"\n >\n {icon && <span className=\"eidos-tab-icon\">{renderIcon(icon, 'eidos-tab-icon-svg')}</span>}\n <span className=\"eidos-tab-label\">{children}</span>\n </button>\n );\n};\n\nTab.displayName = 'Tab';\n\n// ============================================================================\n// TAB PANEL - content region shown when its value matches the active tab\n// Defined before Tabs for the same `child.type === TabPanel` check.\n// ============================================================================\n\nexport const TabPanel: React.FC<TabPanelProps> = ({ value, children, className = '' }) => {\n const { activeValue } = useTabsContext();\n const isActive = activeValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n id={`eidos-tabpanel-${value}`}\n role=\"tabpanel\"\n aria-labelledby={`eidos-tab-${value}`}\n tabIndex={0}\n className={['eidos-tab-panel', className].filter(Boolean).join(' ')}\n >\n {children}\n </div>\n );\n};\n\nTabPanel.displayName = 'TabPanel';\n\n// ============================================================================\n// TABS - root container; provides context, separates tab/panel children\n// ============================================================================\n\nexport const Tabs: React.FC<TabsProps> = ({\n value,\n defaultValue,\n onChange,\n variant = 'line',\n size = 'md',\n color = 'primary',\n fullWidth = false,\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n const [localValue, setLocalValue] = useState(defaultValue ?? '');\n const activeValue = isControlled ? value! : localValue;\n\n const listRef = useRef<HTMLDivElement>(null);\n const indicatorRef = useRef<HTMLSpanElement>(null);\n // Tracks whether the indicator has been positioned at least once so we can\n // skip the CSS transition on the very first render and avoid it animating\n // from the top-left corner to the initial tab.\n const hasPositioned = useRef(false);\n\n useLayoutEffect(() => {\n if (variant !== 'line') return;\n const list = listRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n // Suppress the CSS transition on the initial paint so the indicator snaps\n // into place rather than sliding from 0.\n if (!hasPositioned.current) {\n indicator.style.transition = 'none';\n }\n\n indicator.style.left = `${activeTab.offsetLeft}px`;\n indicator.style.width = `${activeTab.offsetWidth}px`;\n\n if (!hasPositioned.current) {\n // Force a synchronous reflow so the browser commits the no-transition\n // position before we re-enable the transition for future clicks.\n void indicator.offsetWidth;\n indicator.style.transition = '';\n hasPositioned.current = true;\n }\n }, [activeValue, variant, fullWidth]);\n\n const onSelect = (newValue: string) => {\n if (!isControlled) {\n setLocalValue(newValue);\n }\n onChange?.(newValue);\n };\n\n // Separate Tab and TabPanel children so the list and panels render in\n // distinct DOM regions, regardless of how the consumer orders them.\n const tabChildren: React.ReactElement[] = [];\n const panelChildren: React.ReactElement[] = [];\n\n React.Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return;\n if (child.type === Tab) {\n tabChildren.push(child);\n } else if (child.type === TabPanel) {\n panelChildren.push(child);\n }\n });\n\n const rootClasses = ['eidos-tabs', `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className]\n .filter(Boolean)\n .join(' ');\n\n const listClasses = [\n 'eidos-tabs-list',\n `eidos-tabs-list--${size}`,\n fullWidth && 'eidos-tabs-list--fullWidth',\n ]\n .filter(Boolean)\n .join(' ');\n\n const contextValue: TabsContextValue = {\n activeValue,\n onSelect,\n variant,\n size,\n color,\n fullWidth,\n listRef,\n };\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={rootClasses}>\n <div ref={listRef} className={listClasses} role=\"tablist\">\n {tabChildren}\n {variant === 'line' && (\n <span ref={indicatorRef} className=\"eidos-tabs-indicator\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"eidos-tabs-panels\">{panelChildren}</div>\n </div>\n </TabsContext.Provider>\n );\n};\n\nTabs.displayName = 'Tabs';\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-VHQTDITV.cjs","../src/components/Tabs/Tabs.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-VHQTDITV.cjs","sourcesContent":[null,"import React, { useState, useRef, useContext, useLayoutEffect } from 'react';\nimport { renderIcon } from '../../utils';\nimport type { TabsProps, TabProps, TabPanelProps, TabsContextValue } from './Tabs.types';\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null);\n\nconst useTabsContext = (): TabsContextValue => {\n const ctx = useContext(TabsContext);\n if (!ctx) throw new Error('Tab/TabPanel must be used inside <Tabs>');\n return ctx;\n};\n\n// ============================================================================\n// TAB - individual tab trigger button\n// Defined before Tabs so Tabs can use `child.type === Tab` for child separation.\n// ============================================================================\n\nexport const Tab: React.FC<TabProps> = ({\n value,\n children,\n disabled = false,\n icon,\n className = '',\n}) => {\n const { activeValue, onSelect, size, fullWidth, listRef } = useTabsContext();\n const isActive = activeValue === value;\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n const list = listRef.current;\n if (!list) return;\n\n const allTabs = Array.from(\n list.querySelectorAll('[role=\"tab\"]:not([disabled])'),\n ) as HTMLElement[];\n const currentIndex = allTabs.indexOf(e.currentTarget);\n if (currentIndex === -1) return;\n\n let targetIndex = -1;\n if (e.key === 'ArrowRight') targetIndex = (currentIndex + 1) % allTabs.length;\n else if (e.key === 'ArrowLeft')\n targetIndex = (currentIndex - 1 + allTabs.length) % allTabs.length;\n else if (e.key === 'Home') targetIndex = 0;\n else if (e.key === 'End') targetIndex = allTabs.length - 1;\n\n if (targetIndex !== -1) {\n e.preventDefault();\n allTabs[targetIndex].focus();\n }\n };\n\n const tabClasses = [\n 'eidos-tab',\n `eidos-tab--${size}`,\n isActive && 'eidos-tab--active',\n disabled && 'eidos-tab--disabled',\n fullWidth && 'eidos-tab--fullWidth',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <button\n id={`eidos-tab-${value}`}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`eidos-tabpanel-${value}`}\n tabIndex={isActive ? 0 : -1}\n disabled={disabled}\n className={tabClasses}\n onClick={() => !disabled && onSelect(value)}\n onKeyDown={handleKeyDown}\n type=\"button\"\n >\n {icon && <span className=\"eidos-tab-icon\">{renderIcon(icon, 'eidos-tab-icon-svg')}</span>}\n <span className=\"eidos-tab-label\">{children}</span>\n </button>\n );\n};\n\nTab.displayName = 'Tab';\n\n// ============================================================================\n// TAB PANEL - content region shown when its value matches the active tab\n// Defined before Tabs for the same `child.type === TabPanel` check.\n// ============================================================================\n\nexport const TabPanel: React.FC<TabPanelProps> = ({ value, children, className = '' }) => {\n const { activeValue } = useTabsContext();\n const isActive = activeValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n id={`eidos-tabpanel-${value}`}\n role=\"tabpanel\"\n aria-labelledby={`eidos-tab-${value}`}\n tabIndex={0}\n className={['eidos-tab-panel', className].filter(Boolean).join(' ')}\n >\n {children}\n </div>\n );\n};\n\nTabPanel.displayName = 'TabPanel';\n\n// ============================================================================\n// TABS - root container; provides context, separates tab/panel children\n// ============================================================================\n\nexport const Tabs: React.FC<TabsProps> = ({\n value,\n defaultValue,\n onChange,\n variant = 'line',\n size = 'md',\n color = 'primary',\n fullWidth = false,\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n const [localValue, setLocalValue] = useState(defaultValue ?? '');\n const activeValue = isControlled ? value! : localValue;\n\n const listRef = useRef<HTMLDivElement>(null);\n const indicatorRef = useRef<HTMLSpanElement>(null);\n // Tracks whether the indicator has been positioned at least once so we can\n // skip the CSS transition on the very first render and avoid it animating\n // from the top-left corner to the initial tab.\n const hasPositioned = useRef(false);\n\n useLayoutEffect(() => {\n if (variant !== 'line') return;\n const list = listRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n // Suppress the CSS transition on the initial paint so the indicator snaps\n // into place rather than sliding from 0.\n if (!hasPositioned.current) {\n indicator.style.transition = 'none';\n }\n\n indicator.style.left = `${activeTab.offsetLeft}px`;\n indicator.style.width = `${activeTab.offsetWidth}px`;\n\n if (!hasPositioned.current) {\n // Force a synchronous reflow so the browser commits the no-transition\n // position before we re-enable the transition for future clicks.\n void indicator.offsetWidth;\n indicator.style.transition = '';\n hasPositioned.current = true;\n }\n }, [activeValue, variant, fullWidth]);\n\n const onSelect = (newValue: string) => {\n if (!isControlled) {\n setLocalValue(newValue);\n }\n onChange?.(newValue);\n };\n\n // Separate Tab and TabPanel children so the list and panels render in\n // distinct DOM regions, regardless of how the consumer orders them.\n const tabChildren: React.ReactElement[] = [];\n const panelChildren: React.ReactElement[] = [];\n\n React.Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return;\n if (child.type === Tab) {\n tabChildren.push(child);\n } else if (child.type === TabPanel) {\n panelChildren.push(child);\n }\n });\n\n const rootClasses = ['eidos-tabs', `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className]\n .filter(Boolean)\n .join(' ');\n\n const listClasses = [\n 'eidos-tabs-list',\n `eidos-tabs-list--${size}`,\n fullWidth && 'eidos-tabs-list--fullWidth',\n ]\n .filter(Boolean)\n .join(' ');\n\n const contextValue: TabsContextValue = {\n activeValue,\n onSelect,\n variant,\n size,\n color,\n fullWidth,\n listRef,\n };\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={rootClasses}>\n <div ref={listRef} className={listClasses} role=\"tablist\">\n {tabChildren}\n {variant === 'line' && (\n <span ref={indicatorRef} className=\"eidos-tabs-indicator\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"eidos-tabs-panels\">{panelChildren}</div>\n </div>\n </TabsContext.Provider>\n );\n};\n\nTabs.displayName = 'Tabs';\n"]}
|