@coreui/react 5.0.0 → 5.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.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/dropdown/CDropdown.js +2 -1
  3. package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
  4. package/dist/cjs/components/dropdown/utils.d.ts +0 -1
  5. package/dist/cjs/components/dropdown/utils.js +0 -13
  6. package/dist/cjs/components/dropdown/utils.js.map +1 -1
  7. package/dist/cjs/components/popover/CPopover.js +41 -40
  8. package/dist/cjs/components/popover/CPopover.js.map +1 -1
  9. package/dist/cjs/components/tabs/CTab.d.ts +12 -0
  10. package/dist/cjs/components/tabs/CTab.js +25 -0
  11. package/dist/cjs/components/tabs/CTab.js.map +1 -0
  12. package/dist/cjs/components/tabs/CTabList.d.ts +16 -0
  13. package/dist/cjs/components/tabs/CTabList.js +54 -0
  14. package/dist/cjs/components/tabs/CTabList.js.map +1 -0
  15. package/dist/cjs/components/tabs/CTabPane.d.ts +6 -0
  16. package/dist/cjs/components/tabs/CTabPane.js +4 -2
  17. package/dist/cjs/components/tabs/CTabPane.js.map +1 -1
  18. package/dist/cjs/components/tabs/CTabPanel.d.ts +28 -0
  19. package/dist/cjs/components/tabs/CTabPanel.js +43 -0
  20. package/dist/cjs/components/tabs/CTabPanel.js.map +1 -0
  21. package/dist/cjs/components/tabs/CTabs.d.ts +22 -0
  22. package/dist/cjs/components/tabs/CTabs.js +28 -0
  23. package/dist/cjs/components/tabs/CTabs.js.map +1 -0
  24. package/dist/cjs/components/tabs/index.d.ts +5 -1
  25. package/dist/cjs/components/tooltip/CTooltip.js +44 -40
  26. package/dist/cjs/components/tooltip/CTooltip.js.map +1 -1
  27. package/dist/cjs/hooks/usePopper.d.ts +1 -0
  28. package/dist/cjs/hooks/usePopper.js +11 -4
  29. package/dist/cjs/hooks/usePopper.js.map +1 -1
  30. package/dist/cjs/index.js +8 -0
  31. package/dist/cjs/index.js.map +1 -1
  32. package/dist/cjs/node_modules/react-transition-group/esm/CSSTransition.js +1 -2
  33. package/dist/cjs/node_modules/react-transition-group/esm/CSSTransition.js.map +1 -1
  34. package/dist/cjs/node_modules/react-transition-group/esm/Transition.js +1 -2
  35. package/dist/cjs/node_modules/react-transition-group/esm/Transition.js.map +1 -1
  36. package/dist/cjs/utils/getNextActiveElement.d.ts +2 -0
  37. package/dist/cjs/utils/getNextActiveElement.js +19 -0
  38. package/dist/cjs/utils/getNextActiveElement.js.map +1 -0
  39. package/dist/cjs/utils/index.d.ts +2 -1
  40. package/dist/esm/components/dropdown/CDropdown.js +2 -1
  41. package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
  42. package/dist/esm/components/dropdown/utils.d.ts +0 -1
  43. package/dist/esm/components/dropdown/utils.js +1 -13
  44. package/dist/esm/components/dropdown/utils.js.map +1 -1
  45. package/dist/esm/components/popover/CPopover.js +41 -40
  46. package/dist/esm/components/popover/CPopover.js.map +1 -1
  47. package/dist/esm/components/tabs/CTab.d.ts +12 -0
  48. package/dist/esm/components/tabs/CTab.js +23 -0
  49. package/dist/esm/components/tabs/CTab.js.map +1 -0
  50. package/dist/esm/components/tabs/CTabList.d.ts +16 -0
  51. package/dist/esm/components/tabs/CTabList.js +52 -0
  52. package/dist/esm/components/tabs/CTabList.js.map +1 -0
  53. package/dist/esm/components/tabs/CTabPane.d.ts +6 -0
  54. package/dist/esm/components/tabs/CTabPane.js +4 -2
  55. package/dist/esm/components/tabs/CTabPane.js.map +1 -1
  56. package/dist/esm/components/tabs/CTabPanel.d.ts +28 -0
  57. package/dist/esm/components/tabs/CTabPanel.js +41 -0
  58. package/dist/esm/components/tabs/CTabPanel.js.map +1 -0
  59. package/dist/esm/components/tabs/CTabs.d.ts +22 -0
  60. package/dist/esm/components/tabs/CTabs.js +25 -0
  61. package/dist/esm/components/tabs/CTabs.js.map +1 -0
  62. package/dist/esm/components/tabs/index.d.ts +5 -1
  63. package/dist/esm/components/tooltip/CTooltip.js +44 -40
  64. package/dist/esm/components/tooltip/CTooltip.js.map +1 -1
  65. package/dist/esm/hooks/usePopper.d.ts +1 -0
  66. package/dist/esm/hooks/usePopper.js +11 -4
  67. package/dist/esm/hooks/usePopper.js.map +1 -1
  68. package/dist/esm/index.js +4 -0
  69. package/dist/esm/index.js.map +1 -1
  70. package/dist/esm/node_modules/react-transition-group/esm/CSSTransition.js +1 -2
  71. package/dist/esm/node_modules/react-transition-group/esm/CSSTransition.js.map +1 -1
  72. package/dist/esm/node_modules/react-transition-group/esm/Transition.js +1 -2
  73. package/dist/esm/node_modules/react-transition-group/esm/Transition.js.map +1 -1
  74. package/dist/esm/utils/getNextActiveElement.d.ts +2 -0
  75. package/dist/esm/utils/getNextActiveElement.js +15 -0
  76. package/dist/esm/utils/getNextActiveElement.js.map +1 -0
  77. package/dist/esm/utils/index.d.ts +2 -1
  78. package/package.json +12 -12
  79. package/src/components/dropdown/CDropdown.tsx +2 -2
  80. package/src/components/dropdown/utils.ts +0 -22
  81. package/src/components/popover/CPopover.tsx +51 -66
  82. package/src/components/tabs/CTab.tsx +56 -0
  83. package/src/components/tabs/CTabList.tsx +92 -0
  84. package/src/components/tabs/CTabPane.tsx +9 -2
  85. package/src/components/tabs/CTabPanel.tsx +98 -0
  86. package/src/components/tabs/CTabs.tsx +54 -0
  87. package/src/components/tabs/index.ts +5 -1
  88. package/src/components/tooltip/CTooltip.tsx +55 -66
  89. package/src/hooks/usePopper.ts +15 -5
  90. package/src/utils/getNextActiveElement.ts +23 -0
  91. package/src/utils/index.ts +2 -0
@@ -15,10 +15,10 @@ import { PolymorphicRefForwardingComponent } from '../../helpers'
15
15
  import { useForkedRef, usePopper } from '../../hooks'
16
16
  import { placementPropType } from '../../props'
17
17
  import type { Placements } from '../../types'
18
- import { isRTL } from '../../utils'
18
+ import { getNextActiveElement, isRTL } from '../../utils'
19
19
 
20
20
  import type { Alignments, Directions } from './types'
21
- import { getNextActiveElement, getPlacement } from './utils'
21
+ import { getPlacement } from './utils'
22
22
 
23
23
  export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIElement> {
24
24
  /**
@@ -19,28 +19,6 @@ export const getAlignmentClassNames = (alignment: Alignments) => {
19
19
  return classNames
20
20
  }
21
21
 
22
- export const getNextActiveElement = (
23
- list: HTMLElement[],
24
- activeElement: HTMLElement,
25
- shouldGetNext: boolean,
26
- isCycleAllowed: boolean,
27
- ) => {
28
- const listLength = list.length
29
- let index = list.indexOf(activeElement)
30
-
31
- if (index === -1) {
32
- return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]
33
- }
34
-
35
- index += shouldGetNext ? 1 : -1
36
-
37
- if (isCycleAllowed) {
38
- index = (index + listLength) % listLength
39
- }
40
-
41
- return list[Math.max(0, Math.min(index, listLength - 1))]
42
- }
43
-
44
22
  export const getPlacement = (
45
23
  placement: Placement,
46
24
  direction: string | undefined,
@@ -1,13 +1,12 @@
1
1
  import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
2
  import classNames from 'classnames'
3
3
  import PropTypes from 'prop-types'
4
- import { Transition } from 'react-transition-group'
5
4
 
6
5
  import { CConditionalPortal } from '../conditional-portal'
7
6
  import { useForkedRef, usePopper } from '../../hooks'
8
7
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
8
  import type { Placements, Triggers } from '../../types'
10
- import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
9
+ import { executeAfterTransition, getRTLPlacement } from '../../utils'
11
10
 
12
11
  export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'> {
13
12
  /**
@@ -101,6 +100,7 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
101
100
  const uID = useRef(`popover${Math.floor(Math.random() * 1_000_000)}`)
102
101
 
103
102
  const { initPopper, destroyPopper } = usePopper()
103
+ const [mounted, setMounted] = useState(false)
104
104
  const [_visible, setVisible] = useState(visible)
105
105
 
106
106
  const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
@@ -133,14 +133,39 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
133
133
  setVisible(visible)
134
134
  }, [visible])
135
135
 
136
- const toggleVisible = (visible: boolean) => {
137
- if (visible) {
138
- setTimeout(() => setVisible(true), _delay.show)
139
- return
136
+ useEffect(() => {
137
+ if (_visible) {
138
+ setMounted(true)
139
+
140
+ if (popoverRef.current) {
141
+ popoverRef.current.classList.remove('fade', 'show')
142
+ destroyPopper()
143
+ }
144
+
145
+ setTimeout(() => {
146
+ if (togglerRef.current && popoverRef.current) {
147
+ if (animation) {
148
+ popoverRef.current.classList.add('fade')
149
+ }
150
+
151
+ initPopper(togglerRef.current, popoverRef.current, popperConfig)
152
+ popoverRef.current.classList.add('show')
153
+ onShow && onShow()
154
+ }
155
+ }, _delay.show)
140
156
  }
141
157
 
142
- setTimeout(() => setVisible(false), _delay.hide)
143
- }
158
+ return () => {
159
+ if (popoverRef.current) {
160
+ popoverRef.current.classList.remove('show')
161
+ onHide && onHide()
162
+ executeAfterTransition(() => {
163
+ destroyPopper()
164
+ setMounted(false)
165
+ }, popoverRef.current)
166
+ }
167
+ }
168
+ }, [_visible])
144
169
 
145
170
  return (
146
171
  <>
@@ -150,71 +175,31 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
150
175
  }),
151
176
  ref: togglerRef,
152
177
  ...((trigger === 'click' || trigger.includes('click')) && {
153
- onClick: () => toggleVisible(!_visible),
178
+ onClick: () => setVisible(!_visible),
154
179
  }),
155
180
  ...((trigger === 'focus' || trigger.includes('focus')) && {
156
- onFocus: () => toggleVisible(true),
157
- onBlur: () => toggleVisible(false),
181
+ onFocus: () => setVisible(true),
182
+ onBlur: () => setVisible(false),
158
183
  }),
159
184
  ...((trigger === 'hover' || trigger.includes('hover')) && {
160
- onMouseEnter: () => toggleVisible(true),
161
- onMouseLeave: () => toggleVisible(false),
185
+ onMouseEnter: () => setVisible(true),
186
+ onMouseLeave: () => setVisible(false),
162
187
  }),
163
188
  })}
164
189
  <CConditionalPortal container={container} portal={true}>
165
- <Transition
166
- in={_visible}
167
- mountOnEnter
168
- nodeRef={popoverRef}
169
- onEnter={() => {
170
- if (togglerRef.current && popoverRef.current) {
171
- initPopper(togglerRef.current, popoverRef.current, popperConfig)
172
- }
173
-
174
- onShow
175
- }}
176
- onEntering={() => {
177
- if (togglerRef.current && popoverRef.current) {
178
- popoverRef.current.style.display = 'initial'
179
- }
180
- }}
181
- onExit={onHide}
182
- onExited={() => {
183
- destroyPopper()
184
- }}
185
- timeout={{
186
- enter: 0,
187
- exit: popoverRef.current
188
- ? getTransitionDurationFromElement(popoverRef.current) + 50
189
- : 200,
190
- }}
191
- unmountOnExit
192
- >
193
- {(state) => (
194
- <div
195
- className={classNames(
196
- 'popover',
197
- 'bs-popover-auto',
198
- {
199
- fade: animation,
200
- show: state === 'entered',
201
- },
202
- className,
203
- )}
204
- id={uID.current}
205
- ref={forkedRef}
206
- role="tooltip"
207
- style={{
208
- display: 'none',
209
- }}
210
- {...rest}
211
- >
212
- <div className="popover-arrow"></div>
213
- <div className="popover-header">{title}</div>
214
- <div className="popover-body">{content}</div>
215
- </div>
216
- )}
217
- </Transition>
190
+ {mounted && (
191
+ <div
192
+ className={classNames('popover', 'bs-popover-auto', className)}
193
+ id={uID.current}
194
+ ref={forkedRef}
195
+ role="tooltip"
196
+ {...rest}
197
+ >
198
+ <div className="popover-arrow"></div>
199
+ <div className="popover-header">{title}</div>
200
+ <div className="popover-body">{content}</div>
201
+ </div>
202
+ )}
218
203
  </CConditionalPortal>
219
204
  </>
220
205
  )
@@ -0,0 +1,56 @@
1
+ import React, { forwardRef, HTMLAttributes, useContext } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import classNames from 'classnames'
4
+
5
+ import { TabsContext } from './CTabs'
6
+
7
+ export interface CTabProps extends HTMLAttributes<HTMLButtonElement> {
8
+ /**
9
+ * A string of all className you want applied to the base component.
10
+ */
11
+ className?: string
12
+ /**
13
+ * Item key.
14
+ */
15
+ itemKey: number | string
16
+ }
17
+
18
+ export const CTab = forwardRef<HTMLButtonElement, CTabProps>(
19
+ ({ children, className, itemKey, ...rest }, ref) => {
20
+ const { _activeItemKey, setActiveItemKey, id } = useContext(TabsContext)
21
+
22
+ const isActive = () => itemKey === _activeItemKey
23
+
24
+ return (
25
+ <button
26
+ className={classNames(
27
+ 'nav-link',
28
+ {
29
+ active: isActive(),
30
+ },
31
+ className,
32
+ )}
33
+ id={`${id}${itemKey}-tab`}
34
+ onClick={() => setActiveItemKey(itemKey)}
35
+ onFocus={() => setActiveItemKey(itemKey)}
36
+ role="tab"
37
+ tabIndex={isActive() ? 0 : -1}
38
+ type="button"
39
+ aria-controls={`${id}${itemKey}-tab-pane`}
40
+ aria-selected={isActive()}
41
+ ref={ref}
42
+ {...rest}
43
+ >
44
+ {children}
45
+ </button>
46
+ )
47
+ },
48
+ )
49
+
50
+ CTab.propTypes = {
51
+ children: PropTypes.node,
52
+ className: PropTypes.string,
53
+ itemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
54
+ }
55
+
56
+ CTab.displayName = 'CTab'
@@ -0,0 +1,92 @@
1
+ import React, { forwardRef, HTMLAttributes, KeyboardEvent, useRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import classNames from 'classnames'
4
+
5
+ import { useForkedRef } from '../../hooks'
6
+ import { getNextActiveElement } from '../../utils'
7
+
8
+ export interface CTabListProps extends HTMLAttributes<HTMLDivElement> {
9
+ /**
10
+ * A string of all className you want applied to the base component.
11
+ */
12
+ className?: string
13
+ /**
14
+ * Specify a layout type for component.
15
+ */
16
+ layout?: 'fill' | 'justified'
17
+ /**
18
+ * Set the nav variant to tabs or pills.
19
+ */
20
+ variant?: 'pills' | 'tabs' | 'underline' | 'underline-border'
21
+ }
22
+
23
+ export const CTabList = forwardRef<HTMLDivElement, CTabListProps>(
24
+ ({ children, className, layout, variant, ...rest }, ref) => {
25
+ const tabListRef = useRef<HTMLDivElement>(null)
26
+ const forkedRef = useForkedRef(ref, tabListRef)
27
+
28
+ const handleKeydown = (event: KeyboardEvent<HTMLDivElement>) => {
29
+ if (
30
+ tabListRef.current !== null &&
31
+ (event.key === 'ArrowDown' ||
32
+ event.key === 'ArrowUp' ||
33
+ event.key === 'ArrowLeft' ||
34
+ event.key === 'ArrowRight' ||
35
+ event.key === 'Home' ||
36
+ event.key === 'End')
37
+ ) {
38
+ event.preventDefault()
39
+ const target = event.target as HTMLElement
40
+ // eslint-disable-next-line unicorn/prefer-spread
41
+ const items: HTMLElement[] = Array.from(
42
+ tabListRef.current.querySelectorAll('.nav-link:not(.disabled):not(:disabled)'),
43
+ )
44
+
45
+ let nextActiveElement
46
+
47
+ if (event.key === 'Home' || event.key === 'End') {
48
+ nextActiveElement = event.key === 'End' ? items.at(-1) : items[0]
49
+ } else {
50
+ nextActiveElement = getNextActiveElement(
51
+ items,
52
+ target,
53
+ event.key === 'ArrowDown' || event.key === 'ArrowRight',
54
+ true,
55
+ )
56
+ }
57
+
58
+ if (nextActiveElement) {
59
+ nextActiveElement.focus({ preventScroll: true })
60
+ }
61
+ }
62
+ }
63
+
64
+ return (
65
+ <div
66
+ className={classNames(
67
+ 'nav',
68
+ {
69
+ [`nav-${layout}`]: layout,
70
+ [`nav-${variant}`]: variant,
71
+ },
72
+ className,
73
+ )}
74
+ role="tablist"
75
+ onKeyDown={handleKeydown}
76
+ ref={forkedRef}
77
+ {...rest}
78
+ >
79
+ {children}
80
+ </div>
81
+ )
82
+ },
83
+ )
84
+
85
+ CTabList.propTypes = {
86
+ children: PropTypes.node,
87
+ className: PropTypes.string,
88
+ layout: PropTypes.oneOf(['fill', 'justified']),
89
+ variant: PropTypes.oneOf(['pills', 'tabs', 'underline', 'underline-border']),
90
+ }
91
+
92
+ CTabList.displayName = 'CTabList'
@@ -18,6 +18,12 @@ export interface CTabPaneProps extends HTMLAttributes<HTMLDivElement> {
18
18
  * Callback fired when the component requests to be shown.
19
19
  */
20
20
  onShow?: () => void
21
+ /**
22
+ * Enable fade in and fade out transition.
23
+ *
24
+ * @since 5.1.0
25
+ */
26
+ transition?: boolean
21
27
  /**
22
28
  * Toggle the visibility of component.
23
29
  */
@@ -25,7 +31,7 @@ export interface CTabPaneProps extends HTMLAttributes<HTMLDivElement> {
25
31
  }
26
32
 
27
33
  export const CTabPane = forwardRef<HTMLDivElement, CTabPaneProps>(
28
- ({ children, className, onHide, onShow, visible, ...rest }, ref) => {
34
+ ({ children, className, onHide, onShow, transition = true, visible, ...rest }, ref) => {
29
35
  const tabPaneRef = useRef()
30
36
  const forkedRef = useForkedRef(ref, tabPaneRef)
31
37
 
@@ -35,9 +41,9 @@ export const CTabPane = forwardRef<HTMLDivElement, CTabPaneProps>(
35
41
  <div
36
42
  className={classNames(
37
43
  'tab-pane',
38
- 'fade',
39
44
  {
40
45
  active: visible,
46
+ fade: transition,
41
47
  show: state === 'entered',
42
48
  },
43
49
  className,
@@ -58,6 +64,7 @@ CTabPane.propTypes = {
58
64
  className: PropTypes.string,
59
65
  onHide: PropTypes.func,
60
66
  onShow: PropTypes.func,
67
+ transition: PropTypes.bool,
61
68
  visible: PropTypes.bool,
62
69
  }
63
70
 
@@ -0,0 +1,98 @@
1
+ import React, { HTMLAttributes, forwardRef, useContext, useEffect, useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import classNames from 'classnames'
4
+ import { Transition } from 'react-transition-group'
5
+
6
+ import { TabsContext } from './CTabs'
7
+ import { useForkedRef } from '../../hooks'
8
+ import { getTransitionDurationFromElement } from '../../utils'
9
+
10
+ export interface CTabPanelProps extends HTMLAttributes<HTMLDivElement> {
11
+ /**
12
+ * A string of all className you want applied to the base component.
13
+ */
14
+ className?: string
15
+ /**
16
+ * Item key.
17
+ */
18
+ itemKey: number | string
19
+ /**
20
+ * Callback fired when the component requests to be hidden.
21
+ */
22
+ onHide?: () => void
23
+ /**
24
+ * Callback fired when the component requests to be shown.
25
+ */
26
+ onShow?: () => void
27
+ /**
28
+ * Enable fade in and fade out transition.
29
+ */
30
+ transition?: boolean
31
+ /**
32
+ * Toggle the visibility of component.
33
+ */
34
+ visible?: boolean
35
+ }
36
+
37
+ export const CTabPanel = forwardRef<HTMLDivElement, CTabPanelProps>(
38
+ ({ children, className, itemKey, onHide, onShow, transition = true, visible, ...rest }, ref) => {
39
+ const { _activeItemKey, id } = useContext(TabsContext)
40
+
41
+ const tabPaneRef = useRef()
42
+ const forkedRef = useForkedRef(ref, tabPaneRef)
43
+
44
+ const [_visible, setVisible] = useState(visible || _activeItemKey === itemKey)
45
+
46
+ useEffect(() => {
47
+ visible !== undefined && setVisible(visible)
48
+ }, [visible])
49
+
50
+ useEffect(() => {
51
+ setVisible(_activeItemKey === itemKey)
52
+ }, [_activeItemKey])
53
+
54
+ return (
55
+ <Transition
56
+ in={_visible}
57
+ nodeRef={tabPaneRef}
58
+ onEnter={onShow}
59
+ onExit={onHide}
60
+ timeout={tabPaneRef.current ? getTransitionDurationFromElement(tabPaneRef.current) : 0}
61
+ >
62
+ {(state) => (
63
+ <div
64
+ className={classNames(
65
+ 'tab-pane',
66
+ {
67
+ active: _visible,
68
+ fade: transition,
69
+ show: state === 'entered',
70
+ },
71
+ className,
72
+ )}
73
+ id={`${id}${itemKey}-tab-pane`}
74
+ role="tabpanel"
75
+ aria-labelledby={`${id}${itemKey}-tab`}
76
+ tabIndex={0}
77
+ ref={forkedRef}
78
+ {...rest}
79
+ >
80
+ {children}
81
+ </div>
82
+ )}
83
+ </Transition>
84
+ )
85
+ },
86
+ )
87
+
88
+ CTabPanel.propTypes = {
89
+ children: PropTypes.node,
90
+ className: PropTypes.string,
91
+ itemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
92
+ onHide: PropTypes.func,
93
+ onShow: PropTypes.func,
94
+ transition: PropTypes.bool,
95
+ visible: PropTypes.bool,
96
+ }
97
+
98
+ CTabPanel.displayName = 'CTabPanel'
@@ -0,0 +1,54 @@
1
+ import React, { createContext, forwardRef, HTMLAttributes, useEffect, useId, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import classNames from 'classnames'
4
+
5
+ export interface CTabsProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
6
+ /**
7
+ * The active item key.
8
+ */
9
+ activeItemKey: number | string
10
+ /**
11
+ * A string of all className you want applied to the base component.
12
+ */
13
+ className?: string
14
+ /**
15
+ * The callback is fired when the active tab changes.
16
+ */
17
+ onChange?: (value: number | string) => void
18
+ }
19
+
20
+ export interface TabsContextProps {
21
+ _activeItemKey?: number | string
22
+ setActiveItemKey: React.Dispatch<React.SetStateAction<number | string | undefined>>
23
+ id?: string
24
+ }
25
+
26
+ export const TabsContext = createContext({} as TabsContextProps)
27
+
28
+ export const CTabs = forwardRef<HTMLDivElement, CTabsProps>(
29
+ ({ children, activeItemKey, className, onChange }, ref) => {
30
+ const id = useId()
31
+ const [_activeItemKey, setActiveItemKey] = useState(activeItemKey)
32
+
33
+ useEffect(() => {
34
+ _activeItemKey && onChange && onChange(_activeItemKey)
35
+ }, [_activeItemKey])
36
+
37
+ return (
38
+ <TabsContext.Provider value={{ _activeItemKey, setActiveItemKey, id }}>
39
+ <div className={classNames('tabs', className)} ref={ref}>
40
+ {children}
41
+ </div>
42
+ </TabsContext.Provider>
43
+ )
44
+ },
45
+ )
46
+
47
+ CTabs.propTypes = {
48
+ activeItemKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
49
+ children: PropTypes.node,
50
+ className: PropTypes.string,
51
+ onChange: PropTypes.func,
52
+ }
53
+
54
+ CTabs.displayName = 'CTabs'
@@ -1,4 +1,8 @@
1
+ import { CTab } from './CTab'
1
2
  import { CTabContent } from './CTabContent'
2
3
  import { CTabPane } from './CTabPane'
4
+ import { CTabPanel } from './CTabPanel'
5
+ import { CTabList } from './CTabList'
6
+ import { CTabs } from './CTabs'
3
7
 
4
- export { CTabContent, CTabPane }
8
+ export { CTab, CTabContent, CTabList, CTabPane, CTabPanel, CTabs }