@coreui/react 5.0.0-rc.3 → 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 (115) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/badge/CBadge.d.ts +1 -1
  3. package/dist/cjs/components/card/CCard.d.ts +1 -1
  4. package/dist/cjs/components/dropdown/CDropdown.js +2 -1
  5. package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
  6. package/dist/cjs/components/dropdown/CDropdownToggle.d.ts +1 -1
  7. package/dist/cjs/components/dropdown/utils.d.ts +0 -1
  8. package/dist/cjs/components/dropdown/utils.js +0 -13
  9. package/dist/cjs/components/dropdown/utils.js.map +1 -1
  10. package/dist/cjs/components/nav/CNavGroup.d.ts +1 -1
  11. package/dist/cjs/components/nav/CNavGroupItems.d.ts +1 -1
  12. package/dist/cjs/components/nav/CNavItem.d.ts +1 -1
  13. package/dist/cjs/components/popover/CPopover.js +41 -40
  14. package/dist/cjs/components/popover/CPopover.js.map +1 -1
  15. package/dist/cjs/components/sidebar/CSidebarBrand.d.ts +1 -1
  16. package/dist/cjs/components/sidebar/CSidebarNav.d.ts +1 -1
  17. package/dist/cjs/components/tabs/CTab.d.ts +12 -0
  18. package/dist/cjs/components/tabs/CTab.js +25 -0
  19. package/dist/cjs/components/tabs/CTab.js.map +1 -0
  20. package/dist/cjs/components/tabs/CTabList.d.ts +16 -0
  21. package/dist/cjs/components/tabs/CTabList.js +54 -0
  22. package/dist/cjs/components/tabs/CTabList.js.map +1 -0
  23. package/dist/cjs/components/tabs/CTabPane.d.ts +6 -0
  24. package/dist/cjs/components/tabs/CTabPane.js +4 -2
  25. package/dist/cjs/components/tabs/CTabPane.js.map +1 -1
  26. package/dist/cjs/components/tabs/CTabPanel.d.ts +28 -0
  27. package/dist/cjs/components/tabs/CTabPanel.js +43 -0
  28. package/dist/cjs/components/tabs/CTabPanel.js.map +1 -0
  29. package/dist/cjs/components/tabs/CTabs.d.ts +22 -0
  30. package/dist/cjs/components/tabs/CTabs.js +28 -0
  31. package/dist/cjs/components/tabs/CTabs.js.map +1 -0
  32. package/dist/cjs/components/tabs/index.d.ts +5 -1
  33. package/dist/cjs/components/tooltip/CTooltip.js +44 -40
  34. package/dist/cjs/components/tooltip/CTooltip.js.map +1 -1
  35. package/dist/cjs/hooks/usePopper.d.ts +1 -0
  36. package/dist/cjs/hooks/usePopper.js +11 -4
  37. package/dist/cjs/hooks/usePopper.js.map +1 -1
  38. package/dist/cjs/index.js +8 -0
  39. package/dist/cjs/index.js.map +1 -1
  40. package/dist/cjs/node_modules/react-transition-group/esm/CSSTransition.js +1 -2
  41. package/dist/cjs/node_modules/react-transition-group/esm/CSSTransition.js.map +1 -1
  42. package/dist/cjs/node_modules/react-transition-group/esm/Transition.js +1 -2
  43. package/dist/cjs/node_modules/react-transition-group/esm/Transition.js.map +1 -1
  44. package/dist/cjs/utils/getNextActiveElement.d.ts +2 -0
  45. package/dist/cjs/utils/getNextActiveElement.js +19 -0
  46. package/dist/cjs/utils/getNextActiveElement.js.map +1 -0
  47. package/dist/cjs/utils/index.d.ts +2 -1
  48. package/dist/esm/components/badge/CBadge.d.ts +1 -1
  49. package/dist/esm/components/card/CCard.d.ts +1 -1
  50. package/dist/esm/components/dropdown/CDropdown.js +2 -1
  51. package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
  52. package/dist/esm/components/dropdown/CDropdownToggle.d.ts +1 -1
  53. package/dist/esm/components/dropdown/utils.d.ts +0 -1
  54. package/dist/esm/components/dropdown/utils.js +1 -13
  55. package/dist/esm/components/dropdown/utils.js.map +1 -1
  56. package/dist/esm/components/nav/CNavGroup.d.ts +1 -1
  57. package/dist/esm/components/nav/CNavGroupItems.d.ts +1 -1
  58. package/dist/esm/components/nav/CNavItem.d.ts +1 -1
  59. package/dist/esm/components/popover/CPopover.js +41 -40
  60. package/dist/esm/components/popover/CPopover.js.map +1 -1
  61. package/dist/esm/components/sidebar/CSidebarBrand.d.ts +1 -1
  62. package/dist/esm/components/sidebar/CSidebarNav.d.ts +1 -1
  63. package/dist/esm/components/tabs/CTab.d.ts +12 -0
  64. package/dist/esm/components/tabs/CTab.js +23 -0
  65. package/dist/esm/components/tabs/CTab.js.map +1 -0
  66. package/dist/esm/components/tabs/CTabList.d.ts +16 -0
  67. package/dist/esm/components/tabs/CTabList.js +52 -0
  68. package/dist/esm/components/tabs/CTabList.js.map +1 -0
  69. package/dist/esm/components/tabs/CTabPane.d.ts +6 -0
  70. package/dist/esm/components/tabs/CTabPane.js +4 -2
  71. package/dist/esm/components/tabs/CTabPane.js.map +1 -1
  72. package/dist/esm/components/tabs/CTabPanel.d.ts +28 -0
  73. package/dist/esm/components/tabs/CTabPanel.js +41 -0
  74. package/dist/esm/components/tabs/CTabPanel.js.map +1 -0
  75. package/dist/esm/components/tabs/CTabs.d.ts +22 -0
  76. package/dist/esm/components/tabs/CTabs.js +25 -0
  77. package/dist/esm/components/tabs/CTabs.js.map +1 -0
  78. package/dist/esm/components/tabs/index.d.ts +5 -1
  79. package/dist/esm/components/tooltip/CTooltip.js +44 -40
  80. package/dist/esm/components/tooltip/CTooltip.js.map +1 -1
  81. package/dist/esm/hooks/usePopper.d.ts +1 -0
  82. package/dist/esm/hooks/usePopper.js +11 -4
  83. package/dist/esm/hooks/usePopper.js.map +1 -1
  84. package/dist/esm/index.js +4 -0
  85. package/dist/esm/index.js.map +1 -1
  86. package/dist/esm/node_modules/react-transition-group/esm/CSSTransition.js +1 -2
  87. package/dist/esm/node_modules/react-transition-group/esm/CSSTransition.js.map +1 -1
  88. package/dist/esm/node_modules/react-transition-group/esm/Transition.js +1 -2
  89. package/dist/esm/node_modules/react-transition-group/esm/Transition.js.map +1 -1
  90. package/dist/esm/utils/getNextActiveElement.d.ts +2 -0
  91. package/dist/esm/utils/getNextActiveElement.js +15 -0
  92. package/dist/esm/utils/getNextActiveElement.js.map +1 -0
  93. package/dist/esm/utils/index.d.ts +2 -1
  94. package/package.json +12 -12
  95. package/src/components/badge/CBadge.tsx +1 -1
  96. package/src/components/card/CCard.tsx +1 -1
  97. package/src/components/dropdown/CDropdown.tsx +2 -2
  98. package/src/components/dropdown/CDropdownToggle.tsx +1 -1
  99. package/src/components/dropdown/utils.ts +0 -22
  100. package/src/components/nav/CNavGroup.tsx +1 -1
  101. package/src/components/nav/CNavGroupItems.tsx +1 -1
  102. package/src/components/nav/CNavItem.tsx +1 -1
  103. package/src/components/popover/CPopover.tsx +51 -66
  104. package/src/components/sidebar/CSidebarBrand.tsx +1 -1
  105. package/src/components/sidebar/CSidebarNav.tsx +1 -1
  106. package/src/components/tabs/CTab.tsx +56 -0
  107. package/src/components/tabs/CTabList.tsx +92 -0
  108. package/src/components/tabs/CTabPane.tsx +9 -2
  109. package/src/components/tabs/CTabPanel.tsx +98 -0
  110. package/src/components/tabs/CTabs.tsx +54 -0
  111. package/src/components/tabs/index.ts +5 -1
  112. package/src/components/tooltip/CTooltip.tsx +55 -66
  113. package/src/hooks/usePopper.ts +15 -5
  114. package/src/utils/getNextActiveElement.ts +23 -0
  115. package/src/utils/index.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreui/react",
3
- "version": "5.0.0-rc.3",
3
+ "version": "5.1.0",
4
4
  "description": "UI Components Library for React.js",
5
5
  "keywords": [
6
6
  "react",
@@ -41,31 +41,31 @@
41
41
  "test:update": "jest --coverage --updateSnapshot"
42
42
  },
43
43
  "dependencies": {
44
- "@coreui/coreui": "^5.0.0-rc.3",
44
+ "@coreui/coreui": "^5.0.2",
45
45
  "@popperjs/core": "^2.11.8",
46
46
  "prop-types": "^15.8.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@rollup/plugin-commonjs": "^25.0.7",
49
+ "@rollup/plugin-commonjs": "^25.0.8",
50
50
  "@rollup/plugin-node-resolve": "^15.2.3",
51
51
  "@rollup/plugin-typescript": "^11.1.6",
52
- "@testing-library/jest-dom": "^6.4.2",
53
- "@testing-library/react": "^14.2.2",
52
+ "@testing-library/jest-dom": "^6.4.5",
53
+ "@testing-library/react": "^14.3.1",
54
54
  "@types/jest": "^29.5.12",
55
- "@types/react": "18.2.67",
56
- "@types/react-dom": "^18.2.22",
55
+ "@types/react": "18.3.3",
56
+ "@types/react-dom": "^18.3.0",
57
57
  "@types/react-transition-group": "^4.4.10",
58
58
  "classnames": "^2.5.1",
59
59
  "cross-env": "^7.0.3",
60
60
  "jest": "^29.7.0",
61
61
  "jest-environment-jsdom": "^29.7.0",
62
- "react": "^18.2.0",
63
- "react-dom": "^18.2.0",
62
+ "react": "^18.3.1",
63
+ "react-dom": "^18.3.1",
64
64
  "react-transition-group": "^4.4.5",
65
- "rollup": "^4.13.0",
66
- "ts-jest": "^29.1.2",
65
+ "rollup": "^4.18.0",
66
+ "ts-jest": "^29.1.3",
67
67
  "tslib": "^2.6.2",
68
- "typescript": "^5.4.3"
68
+ "typescript": "^5.4.5"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "react": ">=17",
@@ -39,7 +39,7 @@ export interface CBadgeProps extends HTMLAttributes<HTMLDivElement | HTMLSpanEle
39
39
  * Sets the component's color scheme to one of CoreUI's themed colors, ensuring the text color contrast adheres to the WCAG 4.5:1 contrast ratio standard for accessibility.
40
40
  *
41
41
  * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | string
42
- * @since 5.0.0-rc.3
42
+ * @since 5.0.0
43
43
  */
44
44
  textBgColor?: Colors
45
45
  /**
@@ -26,7 +26,7 @@ export interface CCardProps extends HTMLAttributes<HTMLDivElement> {
26
26
  * Sets the component's color scheme to one of CoreUI's themed colors, ensuring the text color contrast adheres to the WCAG 4.5:1 contrast ratio standard for accessibility.
27
27
  *
28
28
  * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | string
29
- * @since 5.0.0-rc.3
29
+ * @since 5.0.0
30
30
  */
31
31
  textBgColor?: Colors
32
32
  }
@@ -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
  /**
@@ -21,7 +21,7 @@ export interface CDropdownToggleProps extends Omit<CButtonProps, 'type'> {
21
21
  /**
22
22
  * If a dropdown `variant` is set to `nav-item` then render the toggler as a link instead of a button.
23
23
  *
24
- * @since v5.0.0-rc.3
24
+ * @since v5.0.0
25
25
  */
26
26
  navLink?: boolean
27
27
  /**
@@ -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,
@@ -22,7 +22,7 @@ export interface CNavGroupProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
22
22
  /**
23
23
  * Component used for the root node. Either a string to use a HTML element or a component.
24
24
  *
25
- * @since 5.0.0-rc.3
25
+ * @since 5.0.0
26
26
  */
27
27
  as?: ElementType
28
28
  /**
@@ -8,7 +8,7 @@ export interface CNavGroupItemsProps extends HTMLAttributes<HTMLDivElement | HTM
8
8
  /**
9
9
  * Component used for the root node. Either a string to use a HTML element or a component.
10
10
  *
11
- * @since 5.0.0-rc.3
11
+ * @since 5.0.0
12
12
  */
13
13
  as?: ElementType
14
14
  /**
@@ -10,7 +10,7 @@ export interface CNavItemProps extends Omit<CNavLinkProps, 'component'> {
10
10
  /**
11
11
  * Component used for the root node. Either a string to use a HTML element or a component.
12
12
  *
13
- * @since 5.0.0-rc.3
13
+ * @since 5.0.0
14
14
  */
15
15
  as?: ElementType
16
16
  }
@@ -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
  )
@@ -8,7 +8,7 @@ export interface CSidebarBrandProps extends HTMLAttributes<HTMLAnchorElement | H
8
8
  /**
9
9
  * Component used for the root node. Either a string to use a HTML element or a component.
10
10
  *
11
- * @since 5.0.0-rc.3
11
+ * @since 5.0.0
12
12
  */
13
13
  as?: ElementType
14
14
  /**
@@ -16,7 +16,7 @@ export interface CSidebarNavProps extends HTMLAttributes<HTMLUListElement> {
16
16
  /**
17
17
  * Component used for the root node. Either a string to use a HTML element or a component.
18
18
  *
19
- * @since 5.0.0-rc.3
19
+ * @since 5.0.0
20
20
  */
21
21
  as?: ElementType
22
22
  /**
@@ -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'