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.
@@ -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", { ref: listRef, className: listClasses, role: "tablist", children: [
156
- tabChildren,
157
- variant === "line" && /* @__PURE__ */ jsx("span", { ref: indicatorRef, className: "eidos-tabs-indicator", "aria-hidden": "true" })
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-RAZRQ57B.js.map
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", { ref: listRef, className: listClasses, role: "tablist", children: [
156
- tabChildren,
157
- variant === "line" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { ref: indicatorRef, className: "eidos-tabs-indicator", "aria-hidden": "true" })
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-VHQTDITV.cjs.map
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 _chunkVHQTDITVcjs = require('./chunk-VHQTDITV.cjs');
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 = _chunkVHQTDITVcjs.Tab; exports.TabPanel = _chunkVHQTDITVcjs.TabPanel; exports.Table = _chunkGGSII6Y2cjs.Table; exports.TableFiltersDropdown = _chunkLIFUHQH5cjs.TableFiltersDropdown; exports.Tabs = _chunkVHQTDITVcjs.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;
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
- border-bottom: 2px solid var(--gray-200);
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
- will-change: transform;
8003
+ height: 100%;
7870
8004
  }
7871
8005
  .eidos-tab--sm {
7872
8006
  font-size: var(--font-size-xs);
7873
- padding: var(--spacing-xs) var(--spacing-sm);
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-xs) var(--spacing-md);
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-sm) var(--spacing-md);
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 3px rgba(var(--primary-rgb), 0.3);
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 3px rgba(var(--secondary-rgb), 0.3);
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 3px rgba(var(--success-rgb), 0.3);
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 3px rgba(var(--danger-rgb), 0.3);
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 3px rgba(var(--warning-rgb), 0.3);
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 3px rgba(var(--info-rgb), 0.3);
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: -2px;
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: grid;
9052
- grid-template-rows: minmax(50px, auto) 1fr auto;
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
- overflow: auto;
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
@@ -8,7 +8,7 @@ import {
8
8
  Tab,
9
9
  TabPanel,
10
10
  Tabs
11
- } from "./chunk-RAZRQ57B.js";
11
+ } from "./chunk-74MBKLYI.js";
12
12
  import {
13
13
  TagInput
14
14
  } from "./chunk-R4PU2TBM.js";
@@ -2,12 +2,12 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkVHQTDITVcjs = require('../chunk-VHQTDITV.cjs');
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 = _chunkVHQTDITVcjs.Tab; exports.TabPanel = _chunkVHQTDITVcjs.TabPanel; exports.Tabs = _chunkVHQTDITVcjs.Tabs;
12
+ exports.Tab = _chunkKQD7CQ4Zcjs.Tab; exports.TabPanel = _chunkKQD7CQ4Zcjs.TabPanel; exports.Tabs = _chunkKQD7CQ4Zcjs.Tabs;
13
13
  //# sourceMappingURL=index.cjs.map
@@ -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
  }
@@ -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
  }
@@ -2,7 +2,7 @@ import {
2
2
  Tab,
3
3
  TabPanel,
4
4
  Tabs
5
- } from "../chunk-RAZRQ57B.js";
5
+ } from "../chunk-74MBKLYI.js";
6
6
  import "../chunk-KBG77JHC.js";
7
7
  import "../chunk-5WRI5ZAA.js";
8
8
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eidos-ui",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "A modern React component library with TypeScript and SCSS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -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"]}