@cerberus-design/react 0.2.0-next-87c3df1 → 0.2.0-next-1b244ce

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.
Files changed (63) hide show
  1. package/build/legacy/_tsup-dts-rollup.d.ts +19 -12
  2. package/build/legacy/aria-helpers/tabs.aria.d.ts +1 -0
  3. package/build/legacy/aria-helpers/tabs.aria.js +9 -0
  4. package/build/legacy/aria-helpers/tabs.aria.js.map +1 -0
  5. package/build/legacy/chunk-57HOQM4E.js +65 -0
  6. package/build/legacy/chunk-57HOQM4E.js.map +1 -0
  7. package/build/legacy/{chunk-24B4KIPX.js → chunk-HQK7SM56.js} +9 -5
  8. package/build/legacy/chunk-HQK7SM56.js.map +1 -0
  9. package/build/legacy/{chunk-X6PHIZRM.js → chunk-MJB3V6J4.js} +4 -4
  10. package/build/{modern/chunk-BVILNJ6M.js → legacy/chunk-ODSSU3JD.js} +1 -1
  11. package/build/legacy/chunk-ODSSU3JD.js.map +1 -0
  12. package/build/{modern/chunk-UI7OR6PG.js → legacy/chunk-RCE2XXL7.js} +17 -5
  13. package/build/legacy/chunk-RCE2XXL7.js.map +1 -0
  14. package/build/legacy/{chunk-6DXQW4WK.js → chunk-TG5VW7KN.js} +12 -4
  15. package/build/legacy/chunk-TG5VW7KN.js.map +1 -0
  16. package/build/legacy/components/NavMenuTrigger.js +2 -2
  17. package/build/legacy/components/Tab.js +3 -2
  18. package/build/legacy/components/TabList.js +1 -1
  19. package/build/legacy/components/TabPanel.js +2 -2
  20. package/build/legacy/context/tabs.d.ts +1 -0
  21. package/build/legacy/context/tabs.js +3 -1
  22. package/build/legacy/index.d.ts +2 -0
  23. package/build/legacy/index.js +25 -19
  24. package/build/modern/_tsup-dts-rollup.d.ts +19 -12
  25. package/build/modern/aria-helpers/tabs.aria.d.ts +1 -0
  26. package/build/modern/aria-helpers/tabs.aria.js +9 -0
  27. package/build/modern/aria-helpers/tabs.aria.js.map +1 -0
  28. package/build/modern/chunk-57HOQM4E.js +65 -0
  29. package/build/modern/chunk-57HOQM4E.js.map +1 -0
  30. package/build/modern/{chunk-24B4KIPX.js → chunk-HQK7SM56.js} +9 -5
  31. package/build/modern/chunk-HQK7SM56.js.map +1 -0
  32. package/build/modern/{chunk-X6PHIZRM.js → chunk-MJB3V6J4.js} +4 -4
  33. package/build/{legacy/chunk-BVILNJ6M.js → modern/chunk-ODSSU3JD.js} +1 -1
  34. package/build/modern/chunk-ODSSU3JD.js.map +1 -0
  35. package/build/{legacy/chunk-UI7OR6PG.js → modern/chunk-RCE2XXL7.js} +17 -5
  36. package/build/modern/chunk-RCE2XXL7.js.map +1 -0
  37. package/build/modern/{chunk-AMFL3PZV.js → chunk-SWCK7V2N.js} +12 -4
  38. package/build/modern/chunk-SWCK7V2N.js.map +1 -0
  39. package/build/modern/components/NavMenuTrigger.js +2 -2
  40. package/build/modern/components/Tab.js +3 -2
  41. package/build/modern/components/TabList.js +1 -1
  42. package/build/modern/components/TabPanel.js +2 -2
  43. package/build/modern/context/tabs.d.ts +1 -0
  44. package/build/modern/context/tabs.js +3 -1
  45. package/build/modern/index.d.ts +2 -0
  46. package/build/modern/index.js +25 -19
  47. package/package.json +1 -1
  48. package/src/aria-helpers/tabs.aria.ts +70 -0
  49. package/src/components/Tab.tsx +10 -8
  50. package/src/components/TabList.tsx +1 -1
  51. package/src/components/TabPanel.tsx +15 -5
  52. package/src/context/tabs.tsx +11 -6
  53. package/src/index.ts +1 -0
  54. package/build/legacy/chunk-24B4KIPX.js.map +0 -1
  55. package/build/legacy/chunk-6DXQW4WK.js.map +0 -1
  56. package/build/legacy/chunk-BVILNJ6M.js.map +0 -1
  57. package/build/legacy/chunk-UI7OR6PG.js.map +0 -1
  58. package/build/modern/chunk-24B4KIPX.js.map +0 -1
  59. package/build/modern/chunk-AMFL3PZV.js.map +0 -1
  60. package/build/modern/chunk-BVILNJ6M.js.map +0 -1
  61. package/build/modern/chunk-UI7OR6PG.js.map +0 -1
  62. /package/build/legacy/{chunk-X6PHIZRM.js.map → chunk-MJB3V6J4.js.map} +0 -0
  63. /package/build/modern/{chunk-X6PHIZRM.js.map → chunk-MJB3V6J4.js.map} +0 -0
@@ -1,9 +1,11 @@
1
1
  import type { AnchorHTMLAttributes } from 'react';
2
2
  import { ButtonHTMLAttributes } from 'react';
3
+ import { Context } from 'react';
3
4
  import { ElementType } from 'react';
4
5
  import { HTMLAttributes } from 'react';
5
6
  import type { InputHTMLAttributes } from 'react';
6
7
  import { JSX as JSX_2 } from 'react/jsx-runtime';
8
+ import { MutableRefObject } from 'react';
7
9
  import { PropsWithChildren } from 'react';
8
10
  import { ReactNode } from 'react';
9
11
  import { RefObject } from 'react';
@@ -281,12 +283,10 @@ export { ShowProps as ShowProps_alias_1 }
281
283
  * The Tab component provides a tab element to be used in a TabList.
282
284
  * @definition [ARIA Target Size](https://www.w3.org/WAI/WCAG21/Understanding/target-size.html#:~:text=Understanding%20SC%202.5.,%3ATarget%20Size%20(Level%20AAA)&text=The%20size%20of%20the%20target,Equivalent)
283
285
  * @definition [Tab docs](https://cerberus.digitalu.design/react/tabs)
284
- * @param id - the id of the tab (used for aria-labelledby in the panel)
285
- * @param controls - the id of the tab panel that the tab controls
286
- * @param value - the id of the tab that will be tracked as the active tab
286
+ * @param value - the id of the tab that will be tracked as the active tab and used for aria attributes
287
287
  * @example
288
288
  * ```tsx
289
- * <Tab controls="panel:overview" value="overview">
289
+ * <Tab value="overview">
290
290
  * Overview
291
291
  * </Tab>
292
292
  * ```
@@ -315,18 +315,17 @@ export { TabList as TabList_alias_1 }
315
315
  * @module
316
316
  */
317
317
  declare interface TabListProps extends HTMLAttributes<HTMLDivElement> {
318
- description?: string;
318
+ description: string;
319
319
  }
320
320
  export { TabListProps }
321
321
  export { TabListProps as TabListProps_alias_1 }
322
322
 
323
323
  /**
324
324
  * The TabPanel component provides a panel element to be used in a Tabs provider.
325
- * @param id - the id of the tab panel which is used for the Tab's control prop
326
- * @param tab - the id of the tab that will be tracked as the active tab
325
+ * @param tab - the value of the tab that will be tracked as the active tab and used for aria attributes
327
326
  * @example
328
327
  * ```tsx
329
- * <TabPanel id="panel:overview" tab="overview">
328
+ * <TabPanel tab="overview">
330
329
  * Overview content
331
330
  * </TabPanel>
332
331
  * ```
@@ -340,7 +339,6 @@ export { TabPanel as TabPanel_alias_1 }
340
339
  * @module
341
340
  */
342
341
  declare interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {
343
- id: string;
344
342
  tab: string;
345
343
  }
346
344
  export { TabPanelProps }
@@ -351,8 +349,6 @@ export { TabPanelProps as TabPanelProps_alias_1 }
351
349
  * @module
352
350
  */
353
351
  declare interface TabProps extends ButtonHTMLAttributes<HTMLButtonElement> {
354
- id: string;
355
- controls: string;
356
352
  value: string;
357
353
  }
358
354
  export { TabProps }
@@ -361,7 +357,7 @@ export { TabProps as TabProps_alias_1 }
361
357
  /**
362
358
  * The Tabs component provides a context to manage tab state.
363
359
  * @param active - the default active tab id,
364
- * @param cache - whether to cache the active tab state
360
+ * @param cache - whether to cache the active tab state in local storage
365
361
  * @example
366
362
  * ```tsx
367
363
  * <Tabs cache>
@@ -380,12 +376,17 @@ declare function Tabs(props: PropsWithChildren<TabsProps>): JSX.Element;
380
376
  export { Tabs }
381
377
  export { Tabs as Tabs_alias_1 }
382
378
 
379
+ declare const TabsContext: Context<TabsContextValue | null>;
380
+ export { TabsContext }
381
+ export { TabsContext as TabsContext_alias_1 }
382
+
383
383
  /**
384
384
  * This module provides a Tabs component and a hook to access its context.
385
385
  * @module
386
386
  */
387
387
  declare interface TabsContextValue {
388
388
  active: string;
389
+ tabs: MutableRefObject<HTMLButtonElement[]>;
389
390
  onTabUpdate: (active: string) => void;
390
391
  }
391
392
  export { TabsContextValue }
@@ -436,6 +437,12 @@ declare function useTabsContext(): TabsContextValue;
436
437
  export { useTabsContext }
437
438
  export { useTabsContext as useTabsContext_alias_1 }
438
439
 
440
+ declare function useTabsKeyboardNavigation(): {
441
+ ref: (tab: HTMLButtonElement) => void;
442
+ };
443
+ export { useTabsKeyboardNavigation }
444
+ export { useTabsKeyboardNavigation as useTabsKeyboardNavigation_alias_1 }
445
+
439
446
  declare function useTheme<C extends string = DefaultThemes>(defaultTheme?: CustomThemes<C>, defaultColorMode?: ColorModes): ThemeContextValue<C>;
440
447
  export { useTheme }
441
448
  export { useTheme as useTheme_alias_1 }
@@ -0,0 +1 @@
1
+ export { useTabsKeyboardNavigation_alias_1 as useTabsKeyboardNavigation } from '../_tsup-dts-rollup';
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import {
3
+ useTabsKeyboardNavigation
4
+ } from "../chunk-57HOQM4E.js";
5
+ import "../chunk-HQK7SM56.js";
6
+ export {
7
+ useTabsKeyboardNavigation
8
+ };
9
+ //# sourceMappingURL=tabs.aria.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,65 @@
1
+ import {
2
+ useTabsContext
3
+ } from "./chunk-HQK7SM56.js";
4
+
5
+ // src/aria-helpers/tabs.aria.ts
6
+ import { useEffect, useState } from "react";
7
+ function getNextIndex(index, length) {
8
+ return index === length - 1 ? 0 : index + 1;
9
+ }
10
+ function getPrevIndex(index, length) {
11
+ return index === 0 ? length - 1 : index - 1;
12
+ }
13
+ function useTabsKeyboardNavigation() {
14
+ const { tabs } = useTabsContext();
15
+ const [activeTab, setActiveTab] = useState(-1);
16
+ useEffect(() => {
17
+ const handleKeyDown = (event) => {
18
+ const index = activeTab === -1 ? tabs.current.findIndex((tab) => tab.ariaSelected === "true") : activeTab;
19
+ const nextIndex = getNextIndex(index, tabs.current.length);
20
+ const prevIndex = getPrevIndex(index, tabs.current.length);
21
+ if (index === -1)
22
+ return;
23
+ switch (event.key) {
24
+ case "ArrowLeft":
25
+ event.preventDefault();
26
+ setActiveTab(prevIndex);
27
+ tabs.current[prevIndex].focus();
28
+ break;
29
+ case "ArrowRight":
30
+ event.preventDefault();
31
+ setActiveTab(nextIndex);
32
+ tabs.current[nextIndex].focus();
33
+ break;
34
+ case "Home":
35
+ event.preventDefault();
36
+ setActiveTab(0);
37
+ tabs.current[0].focus();
38
+ break;
39
+ case "End":
40
+ event.preventDefault();
41
+ setActiveTab(tabs.current.length - 1);
42
+ tabs.current[tabs.current.length - 1].focus();
43
+ break;
44
+ default:
45
+ break;
46
+ }
47
+ };
48
+ document.addEventListener("keydown", handleKeyDown);
49
+ return () => {
50
+ document.removeEventListener("keydown", handleKeyDown);
51
+ };
52
+ }, [activeTab, tabs.current]);
53
+ return {
54
+ ref: (tab) => {
55
+ if (tab && !tabs.current.includes(tab)) {
56
+ tabs.current.push(tab);
57
+ }
58
+ }
59
+ };
60
+ }
61
+
62
+ export {
63
+ useTabsKeyboardNavigation
64
+ };
65
+ //# sourceMappingURL=chunk-57HOQM4E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/aria-helpers/tabs.aria.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useState } from 'react'\nimport { useTabsContext } from '../context/tabs'\n\nfunction getNextIndex(index: number, length: number) {\n return index === length - 1 ? 0 : index + 1\n}\n\nfunction getPrevIndex(index: number, length: number) {\n return index === 0 ? length - 1 : index - 1\n}\n\nexport function useTabsKeyboardNavigation() {\n const { tabs } = useTabsContext()\n const [activeTab, setActiveTab] = useState(-1)\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const index =\n activeTab === -1\n ? tabs.current.findIndex((tab) => tab.ariaSelected === 'true')\n : activeTab\n const nextIndex = getNextIndex(index, tabs.current.length)\n const prevIndex = getPrevIndex(index, tabs.current.length)\n\n // If the active tab is not found, do nothing\n if (index === -1) return\n\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n setActiveTab(prevIndex)\n tabs.current[prevIndex].focus()\n break\n case 'ArrowRight':\n event.preventDefault()\n setActiveTab(nextIndex)\n tabs.current[nextIndex].focus()\n break\n case 'Home':\n event.preventDefault()\n setActiveTab(0)\n tabs.current[0].focus()\n break\n case 'End':\n event.preventDefault()\n setActiveTab(tabs.current.length - 1)\n tabs.current[tabs.current.length - 1].focus()\n break\n default:\n break\n }\n }\n\n document.addEventListener('keydown', handleKeyDown)\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n }\n }, [activeTab, tabs.current])\n\n return {\n ref: (tab: HTMLButtonElement) => {\n if (tab && !tabs.current.includes(tab)) {\n tabs.current.push(tab)\n }\n },\n }\n}\n"],"mappings":";;;;;AAEA,SAAS,WAAW,gBAAgB;AAGpC,SAAS,aAAa,OAAe,QAAgB;AACnD,SAAO,UAAU,SAAS,IAAI,IAAI,QAAQ;AAC5C;AAEA,SAAS,aAAa,OAAe,QAAgB;AACnD,SAAO,UAAU,IAAI,SAAS,IAAI,QAAQ;AAC5C;AAEO,SAAS,4BAA4B;AAC1C,QAAM,EAAE,KAAK,IAAI,eAAe;AAChC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,EAAE;AAE7C,YAAU,MAAM;AACd,UAAM,gBAAgB,CAAC,UAAyB;AAC9C,YAAM,QACJ,cAAc,KACV,KAAK,QAAQ,UAAU,CAAC,QAAQ,IAAI,iBAAiB,MAAM,IAC3D;AACN,YAAM,YAAY,aAAa,OAAO,KAAK,QAAQ,MAAM;AACzD,YAAM,YAAY,aAAa,OAAO,KAAK,QAAQ,MAAM;AAGzD,UAAI,UAAU;AAAI;AAElB,cAAQ,MAAM,KAAK;AAAA,QACjB,KAAK;AACH,gBAAM,eAAe;AACrB,uBAAa,SAAS;AACtB,eAAK,QAAQ,SAAS,EAAE,MAAM;AAC9B;AAAA,QACF,KAAK;AACH,gBAAM,eAAe;AACrB,uBAAa,SAAS;AACtB,eAAK,QAAQ,SAAS,EAAE,MAAM;AAC9B;AAAA,QACF,KAAK;AACH,gBAAM,eAAe;AACrB,uBAAa,CAAC;AACd,eAAK,QAAQ,CAAC,EAAE,MAAM;AACtB;AAAA,QACF,KAAK;AACH,gBAAM,eAAe;AACrB,uBAAa,KAAK,QAAQ,SAAS,CAAC;AACpC,eAAK,QAAQ,KAAK,QAAQ,SAAS,CAAC,EAAE,MAAM;AAC5C;AAAA,QACF;AACE;AAAA,MACJ;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,aAAa;AAElD,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,aAAa;AAAA,IACvD;AAAA,EACF,GAAG,CAAC,WAAW,KAAK,OAAO,CAAC;AAE5B,SAAO;AAAA,IACL,KAAK,CAAC,QAA2B;AAC/B,UAAI,OAAO,CAAC,KAAK,QAAQ,SAAS,GAAG,GAAG;AACtC,aAAK,QAAQ,KAAK,GAAG;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -4,24 +4,27 @@ import {
4
4
  useContext,
5
5
  useEffect,
6
6
  useMemo,
7
+ useRef,
7
8
  useState
8
9
  } from "react";
9
10
  import { jsx } from "react/jsx-runtime";
10
11
  var TabsContext = createContext(null);
11
12
  function Tabs(props) {
12
13
  const { cache } = props;
13
- const [active, setActive] = useState(() => props.active ?? "");
14
+ const [active, setActive] = useState(() => cache ? "" : props.active ?? "");
15
+ const tabs = useRef([]);
14
16
  const value = useMemo(
15
17
  () => ({
16
18
  active,
19
+ tabs,
17
20
  onTabUpdate: setActive
18
21
  }),
19
22
  [active, setActive]
20
23
  );
21
24
  useEffect(() => {
22
- if (cache) {
23
- const cachedTab = window.localStorage.getItem("cerberus-tabs");
24
- setActive(cachedTab ?? active);
25
+ const cachedTab = window.localStorage.getItem("cerberus-tabs");
26
+ if (cache && cachedTab) {
27
+ setActive(cachedTab);
25
28
  }
26
29
  }, [cache]);
27
30
  useEffect(() => {
@@ -40,7 +43,8 @@ function useTabsContext() {
40
43
  }
41
44
 
42
45
  export {
46
+ TabsContext,
43
47
  Tabs,
44
48
  useTabsContext
45
49
  };
46
- //# sourceMappingURL=chunk-24B4KIPX.js.map
50
+ //# sourceMappingURL=chunk-HQK7SM56.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/context/tabs.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type MutableRefObject,\n type PropsWithChildren,\n} from 'react'\n\n/**\n * This module provides a Tabs component and a hook to access its context.\n * @module\n */\n\nexport interface TabsContextValue {\n active: string\n tabs: MutableRefObject<HTMLButtonElement[]>\n onTabUpdate: (active: string) => void\n}\n\nexport const TabsContext = createContext<TabsContextValue | null>(null)\n\nexport interface TabsProps {\n active?: string\n cache?: boolean\n}\n\n/**\n * The Tabs component provides a context to manage tab state.\n * @param active - the default active tab id,\n * @param cache - whether to cache the active tab state in local storage\n * @example\n * ```tsx\n * <Tabs cache>\n * <TabList description=\"Button details\">\n * <Tab id=\"overview\">Overview</Tab>\n * <Tab id=\"guidelines\">Guidelines</Tab>\n * </TabList>\n * <TabPanels>\n * <TabPanel id=\"overview\">Overview content</TabPanel>\n * <TabPanel id=\"guidelines\">Guidelines content</TabPanel>\n * </TabPanels>\n * </Tabs>\n * ```\n */\nexport function Tabs(props: PropsWithChildren<TabsProps>): JSX.Element {\n const { cache } = props\n const [active, setActive] = useState(() => (cache ? '' : props.active ?? ''))\n const tabs = useRef<HTMLButtonElement[]>([])\n\n const value = useMemo(\n () => ({\n active,\n tabs,\n onTabUpdate: setActive,\n }),\n [active, setActive],\n )\n\n useEffect(() => {\n const cachedTab = window.localStorage.getItem('cerberus-tabs')\n if (cache && cachedTab) {\n setActive(cachedTab)\n }\n }, [cache])\n\n useEffect(() => {\n if (cache) {\n window.localStorage.setItem('cerberus-tabs', active)\n }\n }, [active, cache])\n\n return (\n <TabsContext.Provider value={value}>{props.children}</TabsContext.Provider>\n )\n}\n\nexport function useTabsContext(): TabsContextValue {\n const context = useContext(TabsContext)\n if (!context) {\n throw new Error('useTabsContext must be used within a Tabs Provider.')\n }\n return context\n}\n"],"mappings":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAkEH;AArDG,IAAM,cAAc,cAAuC,IAAI;AAyB/D,SAAS,KAAK,OAAkD;AACrE,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,MAAO,QAAQ,KAAK,MAAM,UAAU,EAAG;AAC5E,QAAM,OAAO,OAA4B,CAAC,CAAC;AAE3C,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,aAAa;AAAA,IACf;AAAA,IACA,CAAC,QAAQ,SAAS;AAAA,EACpB;AAEA,YAAU,MAAM;AACd,UAAM,YAAY,OAAO,aAAa,QAAQ,eAAe;AAC7D,QAAI,SAAS,WAAW;AACtB,gBAAU,SAAS;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,KAAK,CAAC;AAEV,YAAU,MAAM;AACd,QAAI,OAAO;AACT,aAAO,aAAa,QAAQ,iBAAiB,MAAM;AAAA,IACrD;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,SACE,oBAAC,YAAY,UAAZ,EAAqB,OAAe,gBAAM,UAAS;AAExD;AAEO,SAAS,iBAAmC;AACjD,QAAM,UAAU,WAAW,WAAW;AACtC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACA,SAAO;AACT;","names":[]}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  useNavMenuContext
3
3
  } from "./chunk-KJUCHZHV.js";
4
- import {
5
- createNavTriggerProps
6
- } from "./chunk-JF76VIL3.js";
7
4
  import {
8
5
  Show
9
6
  } from "./chunk-R4H3352X.js";
7
+ import {
8
+ createNavTriggerProps
9
+ } from "./chunk-JF76VIL3.js";
10
10
 
11
11
  // src/components/NavMenuTrigger.tsx
12
12
  import {
@@ -79,4 +79,4 @@ function NavMenuTrigger(props) {
79
79
  export {
80
80
  NavMenuTrigger
81
81
  };
82
- //# sourceMappingURL=chunk-X6PHIZRM.js.map
82
+ //# sourceMappingURL=chunk-MJB3V6J4.js.map
@@ -25,4 +25,4 @@ function TabList(props) {
25
25
  export {
26
26
  TabList
27
27
  };
28
- //# sourceMappingURL=chunk-BVILNJ6M.js.map
28
+ //# sourceMappingURL=chunk-ODSSU3JD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/TabList.tsx"],"sourcesContent":["import { cx } from '@cerberus/styled-system/css'\nimport { hstack } from '@cerberus/styled-system/patterns'\nimport type { HTMLAttributes, PropsWithChildren } from 'react'\n\n/**\n * This module provides a TabList component.\n * @module\n */\n\nexport interface TabListProps extends HTMLAttributes<HTMLDivElement> {\n description: string\n}\n\n/**\n * The TabList component provides a container for tab elements.\n * @param description - a description of what the tab list contains\n * @example\n * ```tsx\n * <TabList description=\"Button details\">\n * <Tab id=\"overview\">Overview</Tab>\n * <Tab id=\"guidelines\">Guidelines</Tab>\n * </TabList>\n * ```\n */\nexport function TabList(props: PropsWithChildren<TabListProps>) {\n const { description, ...nativeProps } = props\n return (\n <div\n {...nativeProps}\n aria-describedby={description}\n className={cx(\n nativeProps.className,\n hstack({\n borderBottom: '1px solid',\n borderBottomColor: 'action.border.100',\n gap: '0',\n w: 'full',\n }),\n )}\n />\n )\n}\n"],"mappings":";AAAA,SAAS,UAAU;AACnB,SAAS,cAAc;AA0BnB;AAHG,SAAS,QAAQ,OAAwC;AAC9D,QAAM,EAAE,aAAa,GAAG,YAAY,IAAI;AACxC,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,oBAAkB;AAAA,MAClB,WAAW;AAAA,QACT,YAAY;AAAA,QACZ,OAAO;AAAA,UACL,cAAc;AAAA,UACd,mBAAmB;AAAA,UACnB,KAAK;AAAA,UACL,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -1,9 +1,9 @@
1
- import {
2
- useTabsContext
3
- } from "./chunk-24B4KIPX.js";
4
1
  import {
5
2
  Show
6
3
  } from "./chunk-R4H3352X.js";
4
+ import {
5
+ useTabsContext
6
+ } from "./chunk-HQK7SM56.js";
7
7
 
8
8
  // src/components/TabPanel.tsx
9
9
  import { css, cx } from "@cerberus/styled-system/css";
@@ -19,7 +19,19 @@ function TabPanel(props) {
19
19
  ...nativeProps,
20
20
  ...isActive && { tabIndex: 0 },
21
21
  "aria-labelledby": tab,
22
- className: cx(nativeProps.className, css()),
22
+ className: cx(
23
+ nativeProps.className,
24
+ css({
25
+ rounded: "md",
26
+ _focusVisible: {
27
+ boxShadow: "none",
28
+ outline: "3px solid",
29
+ outlineColor: "action.border.focus",
30
+ outlineOffset: "2px"
31
+ }
32
+ })
33
+ ),
34
+ id: `panel:${tab}`,
23
35
  role: "tabpanel"
24
36
  }
25
37
  ) });
@@ -28,4 +40,4 @@ function TabPanel(props) {
28
40
  export {
29
41
  TabPanel
30
42
  };
31
- //# sourceMappingURL=chunk-UI7OR6PG.js.map
43
+ //# sourceMappingURL=chunk-RCE2XXL7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/TabPanel.tsx"],"sourcesContent":["'use client'\n\nimport { css, cx } from '@cerberus/styled-system/css'\nimport { useMemo, type HTMLAttributes } from 'react'\nimport { useTabsContext } from '../context/tabs'\nimport { Show } from './Show'\n\n/**\n * This module provides a TabPanel component.\n * @module\n */\n\nexport interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {\n tab: string\n}\n\n/**\n * The TabPanel component provides a panel element to be used in a Tabs provider.\n * @param tab - the value of the tab that will be tracked as the active tab and used for aria attributes\n * @example\n * ```tsx\n * <TabPanel tab=\"overview\">\n * Overview content\n * </TabPanel>\n * ```\n */\nexport function TabPanel(props: TabPanelProps) {\n const { tab, ...nativeProps } = props\n const { active } = useTabsContext()\n const isActive = useMemo(() => active === tab, [active, tab])\n\n return (\n <Show when={isActive}>\n <div\n {...nativeProps}\n {...(isActive && { tabIndex: 0 })}\n aria-labelledby={tab}\n className={cx(\n nativeProps.className,\n css({\n rounded: 'md',\n _focusVisible: {\n boxShadow: 'none',\n outline: '3px solid',\n outlineColor: 'action.border.focus',\n outlineOffset: '2px',\n },\n }),\n )}\n id={`panel:${tab}`}\n role=\"tabpanel\"\n />\n </Show>\n )\n}\n"],"mappings":";;;;;;;;AAEA,SAAS,KAAK,UAAU;AACxB,SAAS,eAAoC;AA8BvC;AAPC,SAAS,SAAS,OAAsB;AAC7C,QAAM,EAAE,KAAK,GAAG,YAAY,IAAI;AAChC,QAAM,EAAE,OAAO,IAAI,eAAe;AAClC,QAAM,WAAW,QAAQ,MAAM,WAAW,KAAK,CAAC,QAAQ,GAAG,CAAC;AAE5D,SACE,oBAAC,QAAK,MAAM,UACV;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAI,YAAY,EAAE,UAAU,EAAE;AAAA,MAC/B,mBAAiB;AAAA,MACjB,WAAW;AAAA,QACT,YAAY;AAAA,QACZ,IAAI;AAAA,UACF,SAAS;AAAA,UACT,eAAe;AAAA,YACb,WAAW;AAAA,YACX,SAAS;AAAA,YACT,cAAc;AAAA,YACd,eAAe;AAAA,UACjB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MACA,IAAI,SAAS,GAAG;AAAA,MAChB,MAAK;AAAA;AAAA,EACP,GACF;AAEJ;","names":[]}
@@ -1,14 +1,18 @@
1
+ import {
2
+ useTabsKeyboardNavigation
3
+ } from "./chunk-57HOQM4E.js";
1
4
  import {
2
5
  useTabsContext
3
- } from "./chunk-24B4KIPX.js";
6
+ } from "./chunk-HQK7SM56.js";
4
7
 
5
8
  // src/components/Tab.tsx
6
9
  import { useMemo } from "react";
7
10
  import { css, cx } from "@cerberus/styled-system/css";
8
11
  import { jsx } from "react/jsx-runtime";
9
12
  function Tab(props) {
10
- const { controls, value, ...nativeProps } = props;
13
+ const { value, ...nativeProps } = props;
11
14
  const { active, onTabUpdate } = useTabsContext();
15
+ const { ref } = useTabsKeyboardNavigation();
12
16
  const isActive = useMemo(() => active === value, [active, value]);
13
17
  function handleClick(e) {
14
18
  props.onClick?.(e);
@@ -19,16 +23,20 @@ function Tab(props) {
19
23
  {
20
24
  ...nativeProps,
21
25
  ...!isActive && { tabIndex: -1 },
22
- "aria-controls": controls,
26
+ "aria-controls": `panel:${value}`,
23
27
  "aria-selected": isActive,
28
+ id: value,
24
29
  className: cx(nativeProps.className, btnStyles),
25
30
  onClick: handleClick,
26
31
  role: "tab",
32
+ ref,
27
33
  value
28
34
  }
29
35
  );
30
36
  }
31
37
  var btnStyles = css({
38
+ borderTopLeftRadius: "md",
39
+ borderTopRightRadius: "md",
32
40
  fontSize: "sm",
33
41
  fontWeight: "600",
34
42
  h: "2.75rem",
@@ -105,4 +113,4 @@ var btnStyles = css({
105
113
  export {
106
114
  Tab
107
115
  };
108
- //# sourceMappingURL=chunk-AMFL3PZV.js.map
116
+ //# sourceMappingURL=chunk-SWCK7V2N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Tab.tsx"],"sourcesContent":["'use client'\n\nimport { useMemo, type ButtonHTMLAttributes, type MouseEvent } from 'react'\nimport { useTabsContext } from '../context/tabs'\nimport { css, cx } from '@cerberus/styled-system/css'\nimport { useTabsKeyboardNavigation } from '../aria-helpers/tabs.aria'\n\n/**\n * This module provides a Tab component.\n * @module\n */\n\nexport interface TabProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n value: string\n}\n\n/**\n * The Tab component provides a tab element to be used in a TabList.\n * @definition [ARIA Target Size](https://www.w3.org/WAI/WCAG21/Understanding/target-size.html#:~:text=Understanding%20SC%202.5.,%3ATarget%20Size%20(Level%20AAA)&text=The%20size%20of%20the%20target,Equivalent)\n * @definition [Tab docs](https://cerberus.digitalu.design/react/tabs)\n * @param value - the id of the tab that will be tracked as the active tab and used for aria attributes\n * @example\n * ```tsx\n * <Tab value=\"overview\">\n * Overview\n * </Tab>\n * ```\n */\nexport function Tab(props: TabProps) {\n const { value, ...nativeProps } = props\n const { active, onTabUpdate } = useTabsContext()\n const { ref } = useTabsKeyboardNavigation()\n const isActive = useMemo(() => active === value, [active, value])\n\n function handleClick(e: MouseEvent<HTMLButtonElement>) {\n props.onClick?.(e)\n onTabUpdate(e.currentTarget.value)\n }\n\n return (\n <button\n {...nativeProps}\n {...(!isActive && { tabIndex: -1 })}\n aria-controls={`panel:${value}`}\n aria-selected={isActive}\n id={value}\n className={cx(nativeProps.className, btnStyles)}\n onClick={handleClick}\n role=\"tab\"\n ref={ref}\n value={value}\n />\n )\n}\n\nconst btnStyles = css({\n borderTopLeftRadius: 'md',\n borderTopRightRadius: 'md',\n fontSize: 'sm',\n fontWeight: '600',\n h: '2.75rem',\n position: 'relative',\n pxi: '4',\n zIndex: 'base',\n _motionSafe: {\n transition: 'all 200ms ease-in-out',\n _before: {\n transitionProperty: 'height',\n transitionDuration: '200ms',\n transitionTimingFunction: 'ease-in-out',\n },\n _after: {\n transitionProperty: 'height',\n transitionDuration: '200ms',\n transitionTimingFunction: 'ease-in-out',\n },\n },\n _before: {\n bgColor: 'action.border.initial',\n bottom: '0',\n content: '\"\"',\n h: '0',\n position: 'absolute',\n left: '0',\n right: '0',\n w: 'full',\n willChange: 'height',\n zIndex: 'decorator',\n },\n _after: {\n borderTopLeftRadius: 'md',\n borderTopRightRadius: 'md',\n bottom: '0',\n bgColor: 'neutral.surface.100',\n content: '\"\"',\n left: '0',\n position: 'absolute',\n right: '0',\n h: '0',\n w: 'full',\n willChange: 'height',\n zIndex: '-1',\n },\n _hover: {\n _after: {\n h: 'full',\n },\n },\n _focusVisible: {\n boxShadow: 'none',\n outline: '3px solid',\n outlineColor: 'action.border.focus',\n outlineOffset: '2px',\n },\n _disabled: {\n cursor: 'not-allowed',\n opacity: '0.5',\n },\n _selected: {\n color: 'action.text.200',\n _before: {\n h: '3px',\n },\n _hover: {\n _after: {\n h: '0',\n },\n },\n },\n})\n"],"mappings":";;;;;;;;AAEA,SAAS,eAA2D;AAEpE,SAAS,KAAK,UAAU;AAoCpB;AAZG,SAAS,IAAI,OAAiB;AACnC,QAAM,EAAE,OAAO,GAAG,YAAY,IAAI;AAClC,QAAM,EAAE,QAAQ,YAAY,IAAI,eAAe;AAC/C,QAAM,EAAE,IAAI,IAAI,0BAA0B;AAC1C,QAAM,WAAW,QAAQ,MAAM,WAAW,OAAO,CAAC,QAAQ,KAAK,CAAC;AAEhE,WAAS,YAAY,GAAkC;AACrD,UAAM,UAAU,CAAC;AACjB,gBAAY,EAAE,cAAc,KAAK;AAAA,EACnC;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAI,CAAC,YAAY,EAAE,UAAU,GAAG;AAAA,MACjC,iBAAe,SAAS,KAAK;AAAA,MAC7B,iBAAe;AAAA,MACf,IAAI;AAAA,MACJ,WAAW,GAAG,YAAY,WAAW,SAAS;AAAA,MAC9C,SAAS;AAAA,MACT,MAAK;AAAA,MACL;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAM,YAAY,IAAI;AAAA,EACpB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,GAAG;AAAA,EACH,UAAU;AAAA,EACV,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,aAAa;AAAA,IACX,YAAY;AAAA,IACZ,SAAS;AAAA,MACP,oBAAoB;AAAA,MACpB,oBAAoB;AAAA,MACpB,0BAA0B;AAAA,IAC5B;AAAA,IACA,QAAQ;AAAA,MACN,oBAAoB;AAAA,MACpB,oBAAoB;AAAA,MACpB,0BAA0B;AAAA,IAC5B;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,GAAG;AAAA,IACH,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,IACP,GAAG;AAAA,IACH,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,qBAAqB;AAAA,IACrB,sBAAsB;AAAA,IACtB,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP,GAAG;AAAA,IACH,GAAG;AAAA,IACH,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,QAAQ;AAAA,MACN,GAAG;AAAA,IACL;AAAA,EACF;AAAA,EACA,eAAe;AAAA,IACb,WAAW;AAAA,IACX,SAAS;AAAA,IACT,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,WAAW;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AAAA,EACA,WAAW;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,MACP,GAAG;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import {
3
3
  NavMenuTrigger
4
- } from "../chunk-X6PHIZRM.js";
4
+ } from "../chunk-MJB3V6J4.js";
5
5
  import "../chunk-KJUCHZHV.js";
6
- import "../chunk-JF76VIL3.js";
7
6
  import "../chunk-R4H3352X.js";
7
+ import "../chunk-JF76VIL3.js";
8
8
  export {
9
9
  NavMenuTrigger
10
10
  };
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import {
3
3
  Tab
4
- } from "../chunk-AMFL3PZV.js";
5
- import "../chunk-24B4KIPX.js";
4
+ } from "../chunk-SWCK7V2N.js";
5
+ import "../chunk-57HOQM4E.js";
6
+ import "../chunk-HQK7SM56.js";
6
7
  export {
7
8
  Tab
8
9
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TabList
3
- } from "../chunk-BVILNJ6M.js";
3
+ } from "../chunk-ODSSU3JD.js";
4
4
  export {
5
5
  TabList
6
6
  };
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import {
3
3
  TabPanel
4
- } from "../chunk-UI7OR6PG.js";
5
- import "../chunk-24B4KIPX.js";
4
+ } from "../chunk-RCE2XXL7.js";
6
5
  import "../chunk-R4H3352X.js";
6
+ import "../chunk-HQK7SM56.js";
7
7
  export {
8
8
  TabPanel
9
9
  };
@@ -1,4 +1,5 @@
1
1
  export { Tabs_alias_1 as Tabs } from '../_tsup-dts-rollup';
2
2
  export { useTabsContext_alias_1 as useTabsContext } from '../_tsup-dts-rollup';
3
3
  export { TabsContextValue_alias_1 as TabsContextValue } from '../_tsup-dts-rollup';
4
+ export { TabsContext_alias_1 as TabsContext } from '../_tsup-dts-rollup';
4
5
  export { TabsProps_alias_1 as TabsProps } from '../_tsup-dts-rollup';
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import {
3
3
  Tabs,
4
+ TabsContext,
4
5
  useTabsContext
5
- } from "../chunk-24B4KIPX.js";
6
+ } from "../chunk-HQK7SM56.js";
6
7
  export {
7
8
  Tabs,
9
+ TabsContext,
8
10
  useTabsContext
9
11
  };
10
12
  //# sourceMappingURL=tabs.js.map
@@ -35,6 +35,7 @@ export { NavMenuContextValue } from './_tsup-dts-rollup';
35
35
  export { Tabs } from './_tsup-dts-rollup';
36
36
  export { useTabsContext } from './_tsup-dts-rollup';
37
37
  export { TabsContextValue } from './_tsup-dts-rollup';
38
+ export { TabsContext } from './_tsup-dts-rollup';
38
39
  export { TabsProps } from './_tsup-dts-rollup';
39
40
  export { ThemeProvider } from './_tsup-dts-rollup';
40
41
  export { useThemeContext } from './_tsup-dts-rollup';
@@ -47,4 +48,5 @@ export { MODE_KEY } from './_tsup-dts-rollup';
47
48
  export { useTheme } from './_tsup-dts-rollup';
48
49
  export { createNavTriggerProps } from './_tsup-dts-rollup';
49
50
  export { NavTriggerAriaValues } from './_tsup-dts-rollup';
51
+ export { useTabsKeyboardNavigation } from './_tsup-dts-rollup';
50
52
  export { Positions } from './_tsup-dts-rollup';
@@ -1,3 +1,9 @@
1
+ import {
2
+ TabPanel
3
+ } from "./chunk-RCE2XXL7.js";
4
+ import {
5
+ Input
6
+ } from "./chunk-LD5ZV46F.js";
1
7
  import {
2
8
  Label
3
9
  } from "./chunk-OXVNTE4A.js";
@@ -10,28 +16,32 @@ import {
10
16
  } from "./chunk-WSQTX34C.js";
11
17
  import {
12
18
  NavMenuTrigger
13
- } from "./chunk-X6PHIZRM.js";
19
+ } from "./chunk-MJB3V6J4.js";
14
20
  import {
15
21
  NavMenu,
16
22
  useNavMenuContext
17
23
  } from "./chunk-KJUCHZHV.js";
24
+ import {
25
+ Show
26
+ } from "./chunk-R4H3352X.js";
18
27
  import {
19
28
  Tab
20
- } from "./chunk-AMFL3PZV.js";
29
+ } from "./chunk-SWCK7V2N.js";
21
30
  import {
22
31
  TabList
23
- } from "./chunk-BVILNJ6M.js";
24
- import {
25
- TabPanel
26
- } from "./chunk-UI7OR6PG.js";
27
- import {
28
- Tabs,
29
- useTabsContext
30
- } from "./chunk-24B4KIPX.js";
32
+ } from "./chunk-ODSSU3JD.js";
31
33
  import "./chunk-55J6XMHW.js";
32
34
  import {
33
35
  createNavTriggerProps
34
36
  } from "./chunk-JF76VIL3.js";
37
+ import {
38
+ useTabsKeyboardNavigation
39
+ } from "./chunk-57HOQM4E.js";
40
+ import {
41
+ Tabs,
42
+ TabsContext,
43
+ useTabsContext
44
+ } from "./chunk-HQK7SM56.js";
35
45
  import {
36
46
  MODE_KEY,
37
47
  THEME_KEY,
@@ -45,19 +55,13 @@ import {
45
55
  import {
46
56
  FieldMessage
47
57
  } from "./chunk-YVUZSAJG.js";
48
- import {
49
- IconButton
50
- } from "./chunk-BPIYUAYS.js";
51
- import {
52
- Input
53
- } from "./chunk-LD5ZV46F.js";
54
- import {
55
- Show
56
- } from "./chunk-R4H3352X.js";
57
58
  import {
58
59
  Field,
59
60
  useFieldContext
60
61
  } from "./chunk-ZAU4JVLL.js";
62
+ import {
63
+ IconButton
64
+ } from "./chunk-BPIYUAYS.js";
61
65
  export {
62
66
  Button,
63
67
  Field,
@@ -76,12 +80,14 @@ export {
76
80
  TabList,
77
81
  TabPanel,
78
82
  Tabs,
83
+ TabsContext,
79
84
  ThemeProvider,
80
85
  createNavTriggerProps,
81
86
  getPosition,
82
87
  useFieldContext,
83
88
  useNavMenuContext,
84
89
  useTabsContext,
90
+ useTabsKeyboardNavigation,
85
91
  useTheme,
86
92
  useThemeContext
87
93
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerberus-design/react",
3
- "version": "0.2.0-next-87c3df1",
3
+ "version": "0.2.0-next-1b244ce",
4
4
  "description": "The Cerberus Design React component library.",
5
5
  "browserslist": "> 0.25%, not dead",
6
6
  "sideEffects": false,
@@ -0,0 +1,70 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+ import { useTabsContext } from '../context/tabs'
5
+
6
+ function getNextIndex(index: number, length: number) {
7
+ return index === length - 1 ? 0 : index + 1
8
+ }
9
+
10
+ function getPrevIndex(index: number, length: number) {
11
+ return index === 0 ? length - 1 : index - 1
12
+ }
13
+
14
+ export function useTabsKeyboardNavigation() {
15
+ const { tabs } = useTabsContext()
16
+ const [activeTab, setActiveTab] = useState(-1)
17
+
18
+ useEffect(() => {
19
+ const handleKeyDown = (event: KeyboardEvent) => {
20
+ const index =
21
+ activeTab === -1
22
+ ? tabs.current.findIndex((tab) => tab.ariaSelected === 'true')
23
+ : activeTab
24
+ const nextIndex = getNextIndex(index, tabs.current.length)
25
+ const prevIndex = getPrevIndex(index, tabs.current.length)
26
+
27
+ // If the active tab is not found, do nothing
28
+ if (index === -1) return
29
+
30
+ switch (event.key) {
31
+ case 'ArrowLeft':
32
+ event.preventDefault()
33
+ setActiveTab(prevIndex)
34
+ tabs.current[prevIndex].focus()
35
+ break
36
+ case 'ArrowRight':
37
+ event.preventDefault()
38
+ setActiveTab(nextIndex)
39
+ tabs.current[nextIndex].focus()
40
+ break
41
+ case 'Home':
42
+ event.preventDefault()
43
+ setActiveTab(0)
44
+ tabs.current[0].focus()
45
+ break
46
+ case 'End':
47
+ event.preventDefault()
48
+ setActiveTab(tabs.current.length - 1)
49
+ tabs.current[tabs.current.length - 1].focus()
50
+ break
51
+ default:
52
+ break
53
+ }
54
+ }
55
+
56
+ document.addEventListener('keydown', handleKeyDown)
57
+
58
+ return () => {
59
+ document.removeEventListener('keydown', handleKeyDown)
60
+ }
61
+ }, [activeTab, tabs.current])
62
+
63
+ return {
64
+ ref: (tab: HTMLButtonElement) => {
65
+ if (tab && !tabs.current.includes(tab)) {
66
+ tabs.current.push(tab)
67
+ }
68
+ },
69
+ }
70
+ }