@cerberus-design/react 0.2.0 → 0.3.1-next-f5a8b6a

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 (69) hide show
  1. package/build/legacy/_tsup-dts-rollup.d.ts +132 -0
  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-36QCFAIS.js +121 -0
  6. package/build/legacy/chunk-36QCFAIS.js.map +1 -0
  7. package/build/legacy/chunk-57HOQM4E.js +65 -0
  8. package/build/legacy/chunk-57HOQM4E.js.map +1 -0
  9. package/build/legacy/chunk-HQK7SM56.js +50 -0
  10. package/build/legacy/chunk-HQK7SM56.js.map +1 -0
  11. package/build/legacy/{chunk-X6PHIZRM.js → chunk-MJB3V6J4.js} +4 -4
  12. package/build/legacy/chunk-ODSSU3JD.js +28 -0
  13. package/build/legacy/chunk-ODSSU3JD.js.map +1 -0
  14. package/build/legacy/chunk-RCE2XXL7.js +43 -0
  15. package/build/legacy/chunk-RCE2XXL7.js.map +1 -0
  16. package/build/legacy/components/NavMenuTrigger.js +2 -2
  17. package/build/legacy/components/Tab.d.ts +2 -0
  18. package/build/legacy/components/Tab.js +10 -0
  19. package/build/legacy/components/Tab.js.map +1 -0
  20. package/build/legacy/components/TabList.d.ts +2 -0
  21. package/build/legacy/components/TabList.js +7 -0
  22. package/build/legacy/components/TabList.js.map +1 -0
  23. package/build/legacy/components/TabPanel.d.ts +2 -0
  24. package/build/legacy/components/TabPanel.js +10 -0
  25. package/build/legacy/components/TabPanel.js.map +1 -0
  26. package/build/legacy/context/tabs.d.ts +5 -0
  27. package/build/legacy/context/tabs.js +12 -0
  28. package/build/legacy/context/tabs.js.map +1 -0
  29. package/build/legacy/index.d.ts +12 -0
  30. package/build/legacy/index.js +34 -10
  31. package/build/modern/_tsup-dts-rollup.d.ts +132 -0
  32. package/build/modern/aria-helpers/tabs.aria.d.ts +1 -0
  33. package/build/modern/aria-helpers/tabs.aria.js +9 -0
  34. package/build/modern/aria-helpers/tabs.aria.js.map +1 -0
  35. package/build/modern/chunk-57HOQM4E.js +65 -0
  36. package/build/modern/chunk-57HOQM4E.js.map +1 -0
  37. package/build/modern/chunk-HQK7SM56.js +50 -0
  38. package/build/modern/chunk-HQK7SM56.js.map +1 -0
  39. package/build/modern/chunk-LAUJGQO2.js +120 -0
  40. package/build/modern/chunk-LAUJGQO2.js.map +1 -0
  41. package/build/modern/{chunk-X6PHIZRM.js → chunk-MJB3V6J4.js} +4 -4
  42. package/build/modern/chunk-ODSSU3JD.js +28 -0
  43. package/build/modern/chunk-ODSSU3JD.js.map +1 -0
  44. package/build/modern/chunk-RCE2XXL7.js +43 -0
  45. package/build/modern/chunk-RCE2XXL7.js.map +1 -0
  46. package/build/modern/components/NavMenuTrigger.js +2 -2
  47. package/build/modern/components/Tab.d.ts +2 -0
  48. package/build/modern/components/Tab.js +10 -0
  49. package/build/modern/components/Tab.js.map +1 -0
  50. package/build/modern/components/TabList.d.ts +2 -0
  51. package/build/modern/components/TabList.js +7 -0
  52. package/build/modern/components/TabList.js.map +1 -0
  53. package/build/modern/components/TabPanel.d.ts +2 -0
  54. package/build/modern/components/TabPanel.js +10 -0
  55. package/build/modern/components/TabPanel.js.map +1 -0
  56. package/build/modern/context/tabs.d.ts +5 -0
  57. package/build/modern/context/tabs.js +12 -0
  58. package/build/modern/context/tabs.js.map +1 -0
  59. package/build/modern/index.d.ts +12 -0
  60. package/build/modern/index.js +34 -10
  61. package/package.json +1 -1
  62. package/src/aria-helpers/tabs.aria.ts +70 -0
  63. package/src/components/Tab.tsx +134 -0
  64. package/src/components/TabList.tsx +42 -0
  65. package/src/components/TabPanel.tsx +55 -0
  66. package/src/context/tabs.tsx +88 -0
  67. package/src/index.ts +5 -0
  68. /package/build/legacy/{chunk-X6PHIZRM.js.map → chunk-MJB3V6J4.js.map} +0 -0
  69. /package/build/modern/{chunk-X6PHIZRM.js.map → chunk-MJB3V6J4.js.map} +0 -0
@@ -0,0 +1,2 @@
1
+ export { Tab_alias_1 as Tab } from '../_tsup-dts-rollup';
2
+ export { TabProps_alias_1 as TabProps } from '../_tsup-dts-rollup';
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import {
3
+ Tab
4
+ } from "../chunk-LAUJGQO2.js";
5
+ import "../chunk-57HOQM4E.js";
6
+ import "../chunk-HQK7SM56.js";
7
+ export {
8
+ Tab
9
+ };
10
+ //# sourceMappingURL=Tab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ export { TabList_alias_1 as TabList } from '../_tsup-dts-rollup';
2
+ export { TabListProps_alias_1 as TabListProps } from '../_tsup-dts-rollup';
@@ -0,0 +1,7 @@
1
+ import {
2
+ TabList
3
+ } from "../chunk-ODSSU3JD.js";
4
+ export {
5
+ TabList
6
+ };
7
+ //# sourceMappingURL=TabList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ export { TabPanel_alias_1 as TabPanel } from '../_tsup-dts-rollup';
2
+ export { TabPanelProps_alias_1 as TabPanelProps } from '../_tsup-dts-rollup';
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import {
3
+ TabPanel
4
+ } from "../chunk-RCE2XXL7.js";
5
+ import "../chunk-R4H3352X.js";
6
+ import "../chunk-HQK7SM56.js";
7
+ export {
8
+ TabPanel
9
+ };
10
+ //# sourceMappingURL=TabPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,5 @@
1
+ export { Tabs_alias_1 as Tabs } from '../_tsup-dts-rollup';
2
+ export { useTabsContext_alias_1 as useTabsContext } from '../_tsup-dts-rollup';
3
+ export { TabsContextValue_alias_1 as TabsContextValue } from '../_tsup-dts-rollup';
4
+ export { TabsContext_alias_1 as TabsContext } from '../_tsup-dts-rollup';
5
+ export { TabsProps_alias_1 as TabsProps } from '../_tsup-dts-rollup';
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import {
3
+ Tabs,
4
+ TabsContext,
5
+ useTabsContext
6
+ } from "../chunk-HQK7SM56.js";
7
+ export {
8
+ Tabs,
9
+ TabsContext,
10
+ useTabsContext
11
+ };
12
+ //# sourceMappingURL=tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -16,6 +16,12 @@ export { NavMenuList } from './_tsup-dts-rollup';
16
16
  export { NavMenuListProps } from './_tsup-dts-rollup';
17
17
  export { NavMenuLink } from './_tsup-dts-rollup';
18
18
  export { NavMenuLinkProps } from './_tsup-dts-rollup';
19
+ export { Tab } from './_tsup-dts-rollup';
20
+ export { TabProps } from './_tsup-dts-rollup';
21
+ export { TabList } from './_tsup-dts-rollup';
22
+ export { TabListProps } from './_tsup-dts-rollup';
23
+ export { TabPanel } from './_tsup-dts-rollup';
24
+ export { TabPanelProps } from './_tsup-dts-rollup';
19
25
  export { Show } from './_tsup-dts-rollup';
20
26
  export { ShowProps } from './_tsup-dts-rollup';
21
27
  export { Field } from './_tsup-dts-rollup';
@@ -26,6 +32,11 @@ export { useNavMenuContext } from './_tsup-dts-rollup';
26
32
  export { NavTriggerRef } from './_tsup-dts-rollup';
27
33
  export { NavMenuRef } from './_tsup-dts-rollup';
28
34
  export { NavMenuContextValue } from './_tsup-dts-rollup';
35
+ export { Tabs } from './_tsup-dts-rollup';
36
+ export { useTabsContext } from './_tsup-dts-rollup';
37
+ export { TabsContextValue } from './_tsup-dts-rollup';
38
+ export { TabsContext } from './_tsup-dts-rollup';
39
+ export { TabsProps } from './_tsup-dts-rollup';
29
40
  export { ThemeProvider } from './_tsup-dts-rollup';
30
41
  export { useThemeContext } from './_tsup-dts-rollup';
31
42
  export { DefaultThemes } from './_tsup-dts-rollup';
@@ -37,4 +48,5 @@ export { MODE_KEY } from './_tsup-dts-rollup';
37
48
  export { useTheme } from './_tsup-dts-rollup';
38
49
  export { createNavTriggerProps } from './_tsup-dts-rollup';
39
50
  export { NavTriggerAriaValues } from './_tsup-dts-rollup';
51
+ export { useTabsKeyboardNavigation } from './_tsup-dts-rollup';
40
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,15 +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";
27
+ import {
28
+ Tab
29
+ } from "./chunk-LAUJGQO2.js";
30
+ import {
31
+ TabList
32
+ } from "./chunk-ODSSU3JD.js";
18
33
  import "./chunk-55J6XMHW.js";
19
34
  import {
20
35
  createNavTriggerProps
21
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";
22
45
  import {
23
46
  MODE_KEY,
24
47
  THEME_KEY,
@@ -32,19 +55,13 @@ import {
32
55
  import {
33
56
  FieldMessage
34
57
  } from "./chunk-YVUZSAJG.js";
35
- import {
36
- IconButton
37
- } from "./chunk-BPIYUAYS.js";
38
- import {
39
- Input
40
- } from "./chunk-LD5ZV46F.js";
41
- import {
42
- Show
43
- } from "./chunk-R4H3352X.js";
44
58
  import {
45
59
  Field,
46
60
  useFieldContext
47
61
  } from "./chunk-ZAU4JVLL.js";
62
+ import {
63
+ IconButton
64
+ } from "./chunk-BPIYUAYS.js";
48
65
  export {
49
66
  Button,
50
67
  Field,
@@ -59,11 +76,18 @@ export {
59
76
  NavMenuTrigger,
60
77
  Show,
61
78
  THEME_KEY,
79
+ Tab,
80
+ TabList,
81
+ TabPanel,
82
+ Tabs,
83
+ TabsContext,
62
84
  ThemeProvider,
63
85
  createNavTriggerProps,
64
86
  getPosition,
65
87
  useFieldContext,
66
88
  useNavMenuContext,
89
+ useTabsContext,
90
+ useTabsKeyboardNavigation,
67
91
  useTheme,
68
92
  useThemeContext
69
93
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerberus-design/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.1-next-f5a8b6a",
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
+ }
@@ -0,0 +1,134 @@
1
+ 'use client'
2
+
3
+ import { useMemo, type ButtonHTMLAttributes, type MouseEvent } from 'react'
4
+ import { useTabsContext } from '../context/tabs'
5
+ import { css, cx } from '@cerberus/styled-system/css'
6
+ import { useTabsKeyboardNavigation } from '../aria-helpers/tabs.aria'
7
+
8
+ /**
9
+ * This module provides a Tab component.
10
+ * @module
11
+ */
12
+
13
+ export interface TabProps extends ButtonHTMLAttributes<HTMLButtonElement> {
14
+ value: string
15
+ }
16
+
17
+ /**
18
+ * The Tab component provides a tab element to be used in a TabList.
19
+ * @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)
20
+ * @definition [Tab docs](https://cerberus.digitalu.design/react/tabs)
21
+ * @param value - the id of the tab that will be tracked as the active tab and used for aria attributes
22
+ * @example
23
+ * ```tsx
24
+ * <Tab value="overview">
25
+ * Overview
26
+ * </Tab>
27
+ * ```
28
+ */
29
+ export function Tab(props: TabProps) {
30
+ const { value, ...nativeProps } = props
31
+ const { active, onTabUpdate } = useTabsContext()
32
+ const { ref } = useTabsKeyboardNavigation()
33
+ const isActive = useMemo(() => active === value, [active, value])
34
+
35
+ function handleClick(e: MouseEvent<HTMLButtonElement>) {
36
+ props.onClick?.(e)
37
+ onTabUpdate(e.currentTarget.value)
38
+ }
39
+
40
+ return (
41
+ <button
42
+ {...nativeProps}
43
+ {...(!isActive && { tabIndex: -1 })}
44
+ aria-controls={`panel:${value}`}
45
+ aria-selected={isActive}
46
+ id={value}
47
+ className={cx(nativeProps.className, btnStyles)}
48
+ onClick={handleClick}
49
+ role="tab"
50
+ ref={ref}
51
+ value={value}
52
+ />
53
+ )
54
+ }
55
+
56
+ const btnStyles = css({
57
+ alignItems: 'center',
58
+ display: 'inline-flex',
59
+ borderTopLeftRadius: 'md',
60
+ borderTopRightRadius: 'md',
61
+ fontSize: 'sm',
62
+ fontWeight: '600',
63
+ gap: '2',
64
+ h: '2.75rem',
65
+ justifyContent: 'center',
66
+ position: 'relative',
67
+ pxi: '4',
68
+ zIndex: 'base',
69
+ _motionSafe: {
70
+ transition: 'all 200ms ease-in-out',
71
+ _before: {
72
+ transitionProperty: 'height',
73
+ transitionDuration: '200ms',
74
+ transitionTimingFunction: 'ease-in-out',
75
+ },
76
+ _after: {
77
+ transitionProperty: 'height',
78
+ transitionDuration: '200ms',
79
+ transitionTimingFunction: 'ease-in-out',
80
+ },
81
+ },
82
+ _before: {
83
+ bgColor: 'action.border.initial',
84
+ bottom: '0',
85
+ content: '""',
86
+ h: '0',
87
+ position: 'absolute',
88
+ left: '0',
89
+ right: '0',
90
+ w: 'full',
91
+ willChange: 'height',
92
+ zIndex: 'decorator',
93
+ },
94
+ _after: {
95
+ borderTopLeftRadius: 'md',
96
+ borderTopRightRadius: 'md',
97
+ bottom: '0',
98
+ bgColor: 'neutral.surface.100',
99
+ content: '""',
100
+ left: '0',
101
+ position: 'absolute',
102
+ right: '0',
103
+ h: '0',
104
+ w: 'full',
105
+ willChange: 'height',
106
+ zIndex: '-1',
107
+ },
108
+ _hover: {
109
+ _after: {
110
+ h: 'full',
111
+ },
112
+ },
113
+ _focusVisible: {
114
+ boxShadow: 'none',
115
+ outline: '3px solid',
116
+ outlineColor: 'action.border.focus',
117
+ outlineOffset: '2px',
118
+ },
119
+ _disabled: {
120
+ cursor: 'not-allowed',
121
+ opacity: '0.5',
122
+ },
123
+ _selected: {
124
+ color: 'action.text.200',
125
+ _before: {
126
+ h: '3px',
127
+ },
128
+ _hover: {
129
+ _after: {
130
+ h: '0',
131
+ },
132
+ },
133
+ },
134
+ })
@@ -0,0 +1,42 @@
1
+ import { cx } from '@cerberus/styled-system/css'
2
+ import { hstack } from '@cerberus/styled-system/patterns'
3
+ import type { HTMLAttributes, PropsWithChildren } from 'react'
4
+
5
+ /**
6
+ * This module provides a TabList component.
7
+ * @module
8
+ */
9
+
10
+ export interface TabListProps extends HTMLAttributes<HTMLDivElement> {
11
+ description: string
12
+ }
13
+
14
+ /**
15
+ * The TabList component provides a container for tab elements.
16
+ * @param description - a description of what the tab list contains
17
+ * @example
18
+ * ```tsx
19
+ * <TabList description="Button details">
20
+ * <Tab id="overview">Overview</Tab>
21
+ * <Tab id="guidelines">Guidelines</Tab>
22
+ * </TabList>
23
+ * ```
24
+ */
25
+ export function TabList(props: PropsWithChildren<TabListProps>) {
26
+ const { description, ...nativeProps } = props
27
+ return (
28
+ <div
29
+ {...nativeProps}
30
+ aria-describedby={description}
31
+ className={cx(
32
+ nativeProps.className,
33
+ hstack({
34
+ borderBottom: '1px solid',
35
+ borderBottomColor: 'action.border.100',
36
+ gap: '0',
37
+ w: 'full',
38
+ }),
39
+ )}
40
+ />
41
+ )
42
+ }
@@ -0,0 +1,55 @@
1
+ 'use client'
2
+
3
+ import { css, cx } from '@cerberus/styled-system/css'
4
+ import { useMemo, type HTMLAttributes } from 'react'
5
+ import { useTabsContext } from '../context/tabs'
6
+ import { Show } from './Show'
7
+
8
+ /**
9
+ * This module provides a TabPanel component.
10
+ * @module
11
+ */
12
+
13
+ export interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {
14
+ tab: string
15
+ }
16
+
17
+ /**
18
+ * The TabPanel component provides a panel element to be used in a Tabs provider.
19
+ * @param tab - the value of the tab that will be tracked as the active tab and used for aria attributes
20
+ * @example
21
+ * ```tsx
22
+ * <TabPanel tab="overview">
23
+ * Overview content
24
+ * </TabPanel>
25
+ * ```
26
+ */
27
+ export function TabPanel(props: TabPanelProps) {
28
+ const { tab, ...nativeProps } = props
29
+ const { active } = useTabsContext()
30
+ const isActive = useMemo(() => active === tab, [active, tab])
31
+
32
+ return (
33
+ <Show when={isActive}>
34
+ <div
35
+ {...nativeProps}
36
+ {...(isActive && { tabIndex: 0 })}
37
+ aria-labelledby={tab}
38
+ className={cx(
39
+ nativeProps.className,
40
+ css({
41
+ rounded: 'md',
42
+ _focusVisible: {
43
+ boxShadow: 'none',
44
+ outline: '3px solid',
45
+ outlineColor: 'action.border.focus',
46
+ outlineOffset: '2px',
47
+ },
48
+ }),
49
+ )}
50
+ id={`panel:${tab}`}
51
+ role="tabpanel"
52
+ />
53
+ </Show>
54
+ )
55
+ }
@@ -0,0 +1,88 @@
1
+ 'use client'
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type MutableRefObject,
11
+ type PropsWithChildren,
12
+ } from 'react'
13
+
14
+ /**
15
+ * This module provides a Tabs component and a hook to access its context.
16
+ * @module
17
+ */
18
+
19
+ export interface TabsContextValue {
20
+ active: string
21
+ tabs: MutableRefObject<HTMLButtonElement[]>
22
+ onTabUpdate: (active: string) => void
23
+ }
24
+
25
+ export const TabsContext = createContext<TabsContextValue | null>(null)
26
+
27
+ export interface TabsProps {
28
+ active?: string
29
+ cache?: boolean
30
+ }
31
+
32
+ /**
33
+ * The Tabs component provides a context to manage tab state.
34
+ * @param active - the default active tab id,
35
+ * @param cache - whether to cache the active tab state in local storage
36
+ * @example
37
+ * ```tsx
38
+ * <Tabs cache>
39
+ * <TabList description="Button details">
40
+ * <Tab id="overview">Overview</Tab>
41
+ * <Tab id="guidelines">Guidelines</Tab>
42
+ * </TabList>
43
+ * <TabPanels>
44
+ * <TabPanel id="overview">Overview content</TabPanel>
45
+ * <TabPanel id="guidelines">Guidelines content</TabPanel>
46
+ * </TabPanels>
47
+ * </Tabs>
48
+ * ```
49
+ */
50
+ export function Tabs(props: PropsWithChildren<TabsProps>): JSX.Element {
51
+ const { cache } = props
52
+ const [active, setActive] = useState(() => (cache ? '' : props.active ?? ''))
53
+ const tabs = useRef<HTMLButtonElement[]>([])
54
+
55
+ const value = useMemo(
56
+ () => ({
57
+ active,
58
+ tabs,
59
+ onTabUpdate: setActive,
60
+ }),
61
+ [active, setActive],
62
+ )
63
+
64
+ useEffect(() => {
65
+ const cachedTab = window.localStorage.getItem('cerberus-tabs')
66
+ if (cache && cachedTab) {
67
+ setActive(cachedTab)
68
+ }
69
+ }, [cache])
70
+
71
+ useEffect(() => {
72
+ if (cache) {
73
+ window.localStorage.setItem('cerberus-tabs', active)
74
+ }
75
+ }, [active, cache])
76
+
77
+ return (
78
+ <TabsContext.Provider value={value}>{props.children}</TabsContext.Provider>
79
+ )
80
+ }
81
+
82
+ export function useTabsContext(): TabsContextValue {
83
+ const context = useContext(TabsContext)
84
+ if (!context) {
85
+ throw new Error('useTabsContext must be used within a Tabs Provider.')
86
+ }
87
+ return context
88
+ }
package/src/index.ts CHANGED
@@ -13,12 +13,16 @@ export * from './components/Label'
13
13
  export * from './components/NavMenuTrigger'
14
14
  export * from './components/NavMenuList'
15
15
  export * from './components/NavMenuLink'
16
+ export * from './components/Tab'
17
+ export * from './components/TabList'
18
+ export * from './components/TabPanel'
16
19
  export * from './components/Show'
17
20
 
18
21
  // context
19
22
 
20
23
  export * from './context/field'
21
24
  export * from './context/navMenu'
25
+ export * from './context/tabs'
22
26
  export * from './context/theme'
23
27
 
24
28
  // hooks
@@ -28,6 +32,7 @@ export * from './hooks/useTheme'
28
32
  // aria-helpers
29
33
 
30
34
  export * from './aria-helpers/nav-menu.aria'
35
+ export * from './aria-helpers/tabs.aria'
31
36
 
32
37
  // shared types
33
38