@coreui/react 5.0.0-alpha.0 → 5.0.0-alpha.2

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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/components/avatar/CAvatar.d.ts +1 -1
  3. package/dist/components/badge/CBadge.d.ts +1 -1
  4. package/dist/components/card/CCard.d.ts +1 -1
  5. package/dist/components/close-button/CCloseButton.d.ts +6 -0
  6. package/dist/components/dropdown/CDropdown.d.ts +1 -0
  7. package/dist/components/modal/CModal.d.ts +6 -0
  8. package/dist/components/nav/CNav.d.ts +1 -1
  9. package/dist/components/navbar/CNavbar.d.ts +1 -1
  10. package/dist/components/offcanvas/COffcanvas.d.ts +4 -0
  11. package/dist/index.es.js +177 -81
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.js +177 -81
  14. package/dist/index.js.map +1 -1
  15. package/dist/types.d.ts +1 -1
  16. package/dist/utils/executeAfterTransition.d.ts +2 -0
  17. package/dist/utils/getTransitionDurationFromElement.d.ts +2 -0
  18. package/dist/utils/index.d.ts +3 -1
  19. package/package.json +2 -2
  20. package/src/components/avatar/CAvatar.tsx +1 -1
  21. package/src/components/badge/CBadge.tsx +1 -1
  22. package/src/components/button/CButton.tsx +1 -1
  23. package/src/components/button/__tests__/__snapshots__/CButton.spec.tsx.snap +0 -1
  24. package/src/components/card/CCard.tsx +1 -1
  25. package/src/components/carousel/CCarousel.tsx +9 -5
  26. package/src/components/carousel/__tests__/__snapshots__/CCarousel.spec.tsx.snap +9 -5
  27. package/src/components/close-button/CCloseButton.tsx +9 -1
  28. package/src/components/dropdown/CDropdown.tsx +43 -1
  29. package/src/components/dropdown/CDropdownMenu.tsx +1 -1
  30. package/src/components/dropdown/CDropdownToggle.tsx +3 -12
  31. package/src/components/modal/CModal.tsx +25 -9
  32. package/src/components/nav/CNav.tsx +2 -2
  33. package/src/components/nav/CNavGroup.tsx +9 -2
  34. package/src/components/nav/__tests__/__snapshots__/CNav.spec.tsx.snap +5 -4
  35. package/src/components/navbar/CNavbar.tsx +2 -2
  36. package/src/components/navbar/__tests__/CNavbar.spec.tsx +1 -1
  37. package/src/components/navbar/__tests__/__snapshots__/CNavbar.spec.tsx.snap +2 -1
  38. package/src/components/offcanvas/COffcanvas.tsx +8 -1
  39. package/src/components/popover/CPopover.tsx +2 -2
  40. package/src/components/progress/__tests__/__snapshots__/CProgress.spec.tsx.snap +10 -5
  41. package/src/components/progress/__tests__/__snapshots__/CProgressBar.spec.tsx.snap +0 -8
  42. package/src/components/sidebar/CSidebar.tsx +1 -1
  43. package/src/components/tooltip/CTooltip.tsx +2 -2
  44. package/src/components/widgets/CWidgetStatsA.tsx +1 -1
  45. package/src/components/widgets/CWidgetStatsB.tsx +2 -2
  46. package/src/components/widgets/CWidgetStatsC.tsx +4 -12
  47. package/src/components/widgets/CWidgetStatsD.tsx +1 -1
  48. package/src/components/widgets/CWidgetStatsE.tsx +1 -1
  49. package/src/components/widgets/CWidgetStatsF.tsx +1 -1
  50. package/src/components/widgets/__tests__/CWidgetStatsA.spec.tsx +1 -1
  51. package/src/components/widgets/__tests__/CWidgetStatsB.spec.tsx +1 -1
  52. package/src/components/widgets/__tests__/CWidgetStatsC.spec.tsx +1 -1
  53. package/src/components/widgets/__tests__/CWidgetStatsD.spec.tsx +1 -1
  54. package/src/components/widgets/__tests__/CWidgetStatsE.spec.tsx +1 -1
  55. package/src/components/widgets/__tests__/CWidgetStatsF.spec.tsx +1 -1
  56. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsA.spec.tsx.snap +1 -1
  57. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsB.spec.tsx.snap +12 -7
  58. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsC.spec.tsx.snap +14 -9
  59. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsD.spec.tsx.snap +2 -2
  60. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsE.spec.tsx.snap +1 -1
  61. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsF.spec.tsx.snap +2 -2
  62. package/src/hooks/usePopper.ts +10 -2
  63. package/src/types.ts +14 -7
  64. package/src/utils/executeAfterTransition.ts +46 -0
  65. package/src/utils/getTransitionDurationFromElement.ts +24 -0
  66. package/src/utils/index.ts +9 -1
package/dist/types.d.ts CHANGED
@@ -2,5 +2,5 @@ export type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
2
2
  export type Colors = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | string;
3
3
  export type Placements = 'auto' | 'auto-start' | 'auto-end' | 'top-end' | 'top' | 'top-start' | 'bottom-end' | 'bottom' | 'bottom-start' | 'right-start' | 'right' | 'right-end' | 'left-start' | 'left' | 'left-end' | undefined;
4
4
  export type Shapes = 'rounded' | 'rounded-top' | 'rounded-end' | 'rounded-bottom' | 'rounded-start' | 'rounded-circle' | 'rounded-pill' | 'rounded-0' | 'rounded-1' | 'rounded-2' | 'rounded-3' | string;
5
- export type TextColors = Colors | 'white' | 'muted' | 'high-emphasis' | 'medium-emphasis' | 'disabled' | 'high-emphasis-inverse' | 'medium-emphasis-inverse' | 'disabled-inverse' | string;
5
+ export type TextColors = Colors | 'primary-emphasis' | 'secondary-emphasis' | 'success-emphasis' | 'danger-emphasis' | 'warning-emphasis' | 'info-emphasis' | 'light-emphasis' | 'body' | 'body-emphasis' | 'body-secondary' | 'body-tertiary' | 'black' | 'black-50' | 'white' | 'white-50' | string;
6
6
  export type Triggers = 'hover' | 'focus' | 'click';
@@ -0,0 +1,2 @@
1
+ declare const executeAfterTransition: (callback: () => void, transitionElement: HTMLElement, waitForTransition?: boolean) => void;
2
+ export default executeAfterTransition;
@@ -0,0 +1,2 @@
1
+ declare const getTransitionDurationFromElement: (element: HTMLElement) => number;
2
+ export default getTransitionDurationFromElement;
@@ -1,4 +1,6 @@
1
+ import executeAfterTransition from './executeAfterTransition';
1
2
  import getRTLPlacement from './getRTLPlacement';
3
+ import getTransitionDurationFromElement from './getTransitionDurationFromElement';
2
4
  import isInViewport from './isInViewport';
3
5
  import isRTL from './isRTL';
4
- export { getRTLPlacement, isInViewport, isRTL };
6
+ export { executeAfterTransition, getRTLPlacement, getTransitionDurationFromElement, isInViewport, isRTL, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreui/react",
3
- "version": "5.0.0-alpha.0",
3
+ "version": "5.0.0-alpha.2",
4
4
  "description": "UI Components Library for React.js",
5
5
  "keywords": [
6
6
  "react",
@@ -60,7 +60,7 @@
60
60
  "typescript": "^5.1.6"
61
61
  },
62
62
  "peerDependencies": {
63
- "@coreui/coreui": "^5.0.0-alpha.0",
63
+ "@coreui/coreui": "^5.0.0-alpha.2",
64
64
  "react": ">=17",
65
65
  "react-dom": ">=17"
66
66
  }
@@ -39,7 +39,7 @@ export interface CAvatarProps extends HTMLAttributes<HTMLDivElement> {
39
39
  /**
40
40
  * Sets the text color of the component to one of CoreUI’s themed colors.
41
41
  *
42
- * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | 'white' | 'muted' | 'high-emphasis' | 'medium-emphasis' | 'disabled' | 'high-emphasis-inverse' | 'medium-emphasis-inverse' | 'disabled-inverse' | string
42
+ * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | 'primary-emphasis' | 'secondary-emphasis' | 'success-emphasis' | 'danger-emphasis' | 'warning-emphasis' | 'info-emphasis' | 'light-emphasis' | 'body' | 'body-emphasis' | 'body-secondary' | 'body-tertiary' | 'black' | 'black-50' | 'white' | 'white-50' | string
43
43
  */
44
44
  textColor?: TextColors
45
45
  }
@@ -37,7 +37,7 @@ export interface CBadgeProps extends HTMLAttributes<HTMLDivElement | HTMLSpanEle
37
37
  /**
38
38
  * Sets the text color of the component to one of CoreUI’s themed colors.
39
39
  *
40
- * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | 'white' | 'muted' | 'high-emphasis' | 'medium-emphasis' | 'disabled' | 'high-emphasis-inverse' | 'medium-emphasis-inverse' | 'disabled-inverse' | string
40
+ * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | 'primary-emphasis' | 'secondary-emphasis' | 'success-emphasis' | 'danger-emphasis' | 'warning-emphasis' | 'info-emphasis' | 'light-emphasis' | 'body' | 'body-emphasis' | 'body-secondary' | 'body-tertiary' | 'black' | 'black-50' | 'white' | 'white-50' | string
41
41
  */
42
42
  textColor?: TextColors
43
43
  }
@@ -77,7 +77,7 @@ export const CButton = forwardRef<HTMLButtonElement | HTMLAnchorElement, CButton
77
77
  return (
78
78
  <CLink
79
79
  component={rest.href ? 'a' : component}
80
- type={type}
80
+ {...(!rest.href && { type: type })}
81
81
  className={classNames(
82
82
  'btn',
83
83
  variant ? `btn-${variant}-${color}` : `btn-${color}`,
@@ -19,7 +19,6 @@ exports[`CButton customize witch href 1`] = `
19
19
  <a
20
20
  class="btn btn-primary"
21
21
  href="/bazinga"
22
- type="button"
23
22
  >
24
23
  Test
25
24
  </a>
@@ -19,7 +19,7 @@ export interface CCardProps extends HTMLAttributes<HTMLDivElement> {
19
19
  /**
20
20
  * Sets the text color context of the component to one of CoreUI’s themed colors.
21
21
  *
22
- * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | 'white' | 'muted' | 'high-emphasis' | 'medium-emphasis' | 'disabled' | 'high-emphasis-inverse' | 'medium-emphasis-inverse' | 'disabled-inverse' | string
22
+ * @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | 'primary-emphasis' | 'secondary-emphasis' | 'success-emphasis' | 'danger-emphasis' | 'warning-emphasis' | 'info-emphasis' | 'light-emphasis' | 'body' | 'body-emphasis' | 'body-secondary' | 'body-tertiary' | 'black' | 'black-50' | 'white' | 'white-50' | string
23
23
  */
24
24
  textColor?: string
25
25
  }
@@ -226,11 +226,11 @@ export const CCarousel = forwardRef<HTMLDivElement, CCarouselProps>(
226
226
  className={classNames(
227
227
  'carousel slide',
228
228
  {
229
- 'carousel-dark': dark,
230
229
  'carousel-fade': transition === 'crossfade',
231
230
  },
232
231
  className,
233
232
  )}
233
+ {...(dark && { 'data-coreui-theme': 'dark' })}
234
234
  onMouseEnter={_pause}
235
235
  onMouseLeave={cycle}
236
236
  {...(touch && { onTouchStart: handleTouchStart, onTouchMove: handleTouchMove })}
@@ -244,20 +244,24 @@ export const CCarousel = forwardRef<HTMLDivElement, CCarouselProps>(
244
244
  }}
245
245
  >
246
246
  {indicators && (
247
- <ol className="carousel-indicators">
247
+ <div className="carousel-indicators">
248
248
  {Array.from({ length: itemsNumber }, (_, i) => i).map((index) => {
249
249
  return (
250
- <li
250
+ <button
251
251
  key={`indicator${index}`}
252
252
  onClick={() => {
253
253
  !animating && handleIndicatorClick(index)
254
254
  }}
255
- className={active === index ? 'active' : ''}
255
+ className={classNames({
256
+ active: active === index
257
+ })}
256
258
  data-coreui-target=""
259
+ {...(active === index && { 'aria-current': true })}
260
+ aria-label={`Slide ${index + 1}`}
257
261
  />
258
262
  )
259
263
  })}
260
- </ol>
264
+ </div>
261
265
  )}
262
266
  <div className="carousel-inner">
263
267
  {Children.map(children, (child, index) => {
@@ -5,22 +5,26 @@ exports[`loads and displays CCarousel component 1`] = `
5
5
  <div
6
6
  class="carousel slide"
7
7
  >
8
- <ol
8
+ <div
9
9
  class="carousel-indicators"
10
10
  >
11
- <li
11
+ <button
12
+ aria-current="true"
13
+ aria-label="Slide 1"
12
14
  class="active"
13
15
  data-coreui-target=""
14
16
  />
15
- <li
17
+ <button
18
+ aria-label="Slide 2"
16
19
  class=""
17
20
  data-coreui-target=""
18
21
  />
19
- <li
22
+ <button
23
+ aria-label="Slide 3"
20
24
  class=""
21
25
  data-coreui-target=""
22
26
  />
23
- </ol>
27
+ </div>
24
28
  <div
25
29
  class="carousel-inner"
26
30
  >
@@ -7,18 +7,24 @@ export interface CCloseButtonProps extends HTMLAttributes<HTMLButtonElement> {
7
7
  * A string of all className you want applied to the base component.
8
8
  */
9
9
  className?: string
10
+ /**
11
+ * Invert the default color.
12
+ */
13
+ dark?: boolean
10
14
  /**
11
15
  * Toggle the disabled state for the component.
12
16
  */
13
17
  disabled?: boolean
14
18
  /**
15
19
  * Change the default color to white.
20
+ *
21
+ * @deprecated 5.0.0
16
22
  */
17
23
  white?: boolean
18
24
  }
19
25
 
20
26
  export const CCloseButton = forwardRef<HTMLButtonElement, CCloseButtonProps>(
21
- ({ className, disabled, white, ...rest }, ref) => {
27
+ ({ className, dark, disabled, white, ...rest }, ref) => {
22
28
  return (
23
29
  <button
24
30
  type="button"
@@ -33,6 +39,7 @@ export const CCloseButton = forwardRef<HTMLButtonElement, CCloseButtonProps>(
33
39
  )}
34
40
  aria-label="Close"
35
41
  disabled={disabled}
42
+ {...(dark && { 'data-coreui-theme': 'dark' })}
36
43
  {...rest}
37
44
  ref={ref}
38
45
  />
@@ -42,6 +49,7 @@ export const CCloseButton = forwardRef<HTMLButtonElement, CCloseButtonProps>(
42
49
 
43
50
  CCloseButton.propTypes = {
44
51
  className: PropTypes.string,
52
+ dark: PropTypes.bool,
45
53
  disabled: PropTypes.bool,
46
54
  white: PropTypes.bool,
47
55
  }
@@ -107,6 +107,28 @@ interface ContextProps extends CDropdownProps {
107
107
  portal: boolean
108
108
  }
109
109
 
110
+ export const getNextActiveElement = (
111
+ list: HTMLElement[],
112
+ activeElement: HTMLElement,
113
+ shouldGetNext: boolean,
114
+ isCycleAllowed: boolean,
115
+ ) => {
116
+ const listLength = list.length
117
+ let index = list.indexOf(activeElement)
118
+
119
+ if (index === -1) {
120
+ return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]
121
+ }
122
+
123
+ index += shouldGetNext ? 1 : -1
124
+
125
+ if (isCycleAllowed) {
126
+ index = (index + listLength) % listLength
127
+ }
128
+
129
+ return list[Math.max(0, Math.min(index, listLength - 1))]
130
+ }
131
+
110
132
  const getPlacement = (
111
133
  placement: Placements,
112
134
  direction: CDropdownProps['direction'],
@@ -207,9 +229,12 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
207
229
 
208
230
  useEffect(() => {
209
231
  if (_visible && dropdownToggleRef.current && dropdownMenuRef.current) {
232
+ dropdownToggleRef.current.focus()
210
233
  popper && initPopper(dropdownToggleRef.current, dropdownMenuRef.current, popperConfig)
211
234
  window.addEventListener('mouseup', handleMouseUp)
212
235
  window.addEventListener('keyup', handleKeyup)
236
+ dropdownToggleRef.current.addEventListener('keydown', handleKeydown)
237
+ dropdownMenuRef.current.addEventListener('keydown', handleKeydown)
213
238
  onShow && onShow()
214
239
  }
215
240
 
@@ -217,10 +242,28 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
217
242
  popper && destroyPopper()
218
243
  window.removeEventListener('mouseup', handleMouseUp)
219
244
  window.removeEventListener('keyup', handleKeyup)
245
+ dropdownToggleRef.current &&
246
+ dropdownToggleRef.current.removeEventListener('keydown', handleKeydown)
247
+ dropdownMenuRef.current &&
248
+ dropdownMenuRef.current.removeEventListener('keydown', handleKeydown)
220
249
  onHide && onHide()
221
250
  }
222
251
  }, [_visible])
223
252
 
253
+ const handleKeydown = (event: KeyboardEvent) => {
254
+ if (_visible && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
255
+ const target = event.target as HTMLElement
256
+ event.preventDefault()
257
+ const items = [].concat(
258
+ ...Element.prototype.querySelectorAll.call(
259
+ dropdownMenuRef.current,
260
+ '.dropdown-item:not(.disabled):not(:disabled)',
261
+ ),
262
+ )
263
+ getNextActiveElement(items, target, event.key === 'ArrowDown', true).focus()
264
+ }
265
+ }
266
+
224
267
  const handleKeyup = (event: KeyboardEvent) => {
225
268
  if (autoClose === false) {
226
269
  return
@@ -263,7 +306,6 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
263
306
  'dropup dropup-center': direction === 'dropup-center',
264
307
  [`${direction}`]:
265
308
  direction && direction !== 'center' && direction !== 'dropup-center',
266
- show: _visible,
267
309
  },
268
310
  className,
269
311
  )}
@@ -46,7 +46,6 @@ export const CDropdownMenu = forwardRef<HTMLDivElement | HTMLUListElement, CDrop
46
46
  className={classNames(
47
47
  'dropdown-menu',
48
48
  {
49
- 'dropdown-menu-dark': dark,
50
49
  show: visible,
51
50
  },
52
51
  alignment && alignmentClassNames(alignment),
@@ -56,6 +55,7 @@ export const CDropdownMenu = forwardRef<HTMLDivElement | HTMLUListElement, CDrop
56
55
  role="menu"
57
56
  aria-hidden={!visible}
58
57
  {...(!popper && { 'data-coreui-popper': 'static' })}
58
+ {...(dark && { 'data-coreui-theme': 'dark' })}
59
59
  {...rest}
60
60
  >
61
61
  {Component === 'ul'
@@ -39,7 +39,7 @@ export const CDropdownToggle: FC<CDropdownToggleProps> = ({
39
39
  trigger = 'click',
40
40
  ...rest
41
41
  }) => {
42
- const { dropdownToggleRef, variant, visible, setVisible } = useContext(CDropdownContext)
42
+ const { dropdownToggleRef, visible, setVisible } = useContext(CDropdownContext)
43
43
 
44
44
  const triggers = {
45
45
  ...((trigger === 'click' || trigger.includes('click')) && {
@@ -59,7 +59,7 @@ export const CDropdownToggle: FC<CDropdownToggleProps> = ({
59
59
  {
60
60
  'dropdown-toggle': caret,
61
61
  'dropdown-toggle-split': split,
62
- 'nav-link': variant === 'nav-item',
62
+ show: visible,
63
63
  },
64
64
  className,
65
65
  ),
@@ -67,7 +67,6 @@ export const CDropdownToggle: FC<CDropdownToggleProps> = ({
67
67
  ...(!rest.disabled && { ...triggers }),
68
68
  }
69
69
 
70
- // We use any because Toggler can be `a` as well as `button`.
71
70
  const Toggler = () => {
72
71
  if (custom && React.isValidElement(children)) {
73
72
  return (
@@ -81,16 +80,8 @@ export const CDropdownToggle: FC<CDropdownToggleProps> = ({
81
80
  )
82
81
  }
83
82
 
84
- if (variant === 'nav-item') {
85
- return (
86
- <a href="#" {...togglerProps} ref={dropdownToggleRef}>
87
- {children}
88
- </a>
89
- )
90
- }
91
-
92
83
  return (
93
- <CButton type="button" {...togglerProps} tabIndex={0} {...rest} ref={dropdownToggleRef}>
84
+ <CButton {...togglerProps} tabIndex={0} {...rest} ref={dropdownToggleRef}>
94
85
  {children}
95
86
  {split && <span className="visually-hidden">Toggle Dropdown</span>}
96
87
  </CButton>
@@ -11,7 +11,7 @@ import PropTypes from 'prop-types'
11
11
  import classNames from 'classnames'
12
12
  import { Transition } from 'react-transition-group'
13
13
 
14
- import { CBackdrop } from '../backdrop/CBackdrop'
14
+ import { CBackdrop } from '../backdrop'
15
15
  import { CConditionalPortal } from '../conditional-portal'
16
16
  import { CModalContent } from './CModalContent'
17
17
  import { CModalDialog } from './CModalDialog'
@@ -35,6 +35,12 @@ export interface CModalProps extends HTMLAttributes<HTMLDivElement> {
35
35
  * @ignore
36
36
  */
37
37
  duration?: number
38
+ /**
39
+ * Puts the focus on the modal when shown.
40
+ *
41
+ * @since v4.10.0
42
+ */
43
+ focus?: boolean
38
44
  /**
39
45
  * Set modal to covers the entire user viewport.
40
46
  */
@@ -96,6 +102,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
96
102
  backdrop = true,
97
103
  className,
98
104
  duration = 150,
105
+ focus = true,
99
106
  fullscreen,
100
107
  keyboard = true,
101
108
  onClose,
@@ -111,6 +118,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
111
118
  },
112
119
  ref,
113
120
  ) => {
121
+ const activeElementRef = useRef<HTMLElement | null>(null)
114
122
  const modalRef = useRef<HTMLDivElement>(null)
115
123
  const modalContentRef = useRef<HTMLDivElement>(null)
116
124
  const forkedRef = useForkedRef(ref, modalRef)
@@ -128,11 +136,16 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
128
136
  }, [visible])
129
137
 
130
138
  useEffect(() => {
131
- document.addEventListener('click', handleClickOutside)
132
- document.addEventListener('keydown', handleKeyDown)
139
+ if (_visible) {
140
+ activeElementRef.current = document.activeElement as HTMLElement | null
141
+ document.addEventListener('mouseup', handleClickOutside)
142
+ document.addEventListener('keydown', handleKeyDown)
143
+ } else {
144
+ activeElementRef.current?.focus()
145
+ }
133
146
 
134
147
  return () => {
135
- document.removeEventListener('click', handleClickOutside)
148
+ document.removeEventListener('mouseup', handleClickOutside)
136
149
  document.removeEventListener('keydown', handleKeyDown)
137
150
  }
138
151
  }, [_visible])
@@ -143,6 +156,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
143
156
  }
144
157
 
145
158
  setVisible(false)
159
+
146
160
  return onClose && onClose()
147
161
  }
148
162
 
@@ -163,7 +177,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
163
177
 
164
178
  setTimeout(
165
179
  () => {
166
- modalRef.current?.focus()
180
+ focus && modalRef.current?.focus()
167
181
  },
168
182
  transition ? duration : 0,
169
183
  )
@@ -225,10 +239,13 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
225
239
  className,
226
240
  )}
227
241
  tabIndex={-1}
228
- role="dialog"
242
+ {...(_visible
243
+ ? { 'aria-modal': true, role: 'dialog' }
244
+ : { 'aria-hidden': 'true' })}
229
245
  style={{
230
246
  ...(state !== 'exited' && { display: 'block' }),
231
247
  }}
248
+ {...rest}
232
249
  ref={forkedRef}
233
250
  >
234
251
  <CModalDialog
@@ -237,9 +254,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
237
254
  scrollable={scrollable}
238
255
  size={size}
239
256
  >
240
- <CModalContent {...rest} ref={modalContentRef}>
241
- {children}
242
- </CModalContent>
257
+ <CModalContent ref={modalContentRef}>{children}</CModalContent>
243
258
  </CModalDialog>
244
259
  </div>
245
260
  </CModalContext.Provider>
@@ -262,6 +277,7 @@ CModal.propTypes = {
262
277
  children: PropTypes.node,
263
278
  className: PropTypes.string,
264
279
  duration: PropTypes.number,
280
+ focus: PropTypes.bool,
265
281
  fullscreen: PropTypes.oneOfType([
266
282
  PropTypes.bool,
267
283
  PropTypes.oneOf<'sm' | 'md' | 'lg' | 'xl' | 'xxl'>(['sm', 'md', 'lg', 'xl', 'xxl']),
@@ -19,7 +19,7 @@ export interface CNavProps
19
19
  /**
20
20
  * Set the nav variant to tabs or pills.
21
21
  */
22
- variant?: 'tabs' | 'pills'
22
+ variant?: 'pills' | 'tabs' | 'underline'
23
23
  }
24
24
 
25
25
  export const CNav = forwardRef<HTMLDivElement | HTMLUListElement | HTMLOListElement, CNavProps>(
@@ -49,7 +49,7 @@ CNav.propTypes = {
49
49
  className: PropTypes.string,
50
50
  component: PropTypes.elementType,
51
51
  layout: PropTypes.oneOf(['fill', 'justified']),
52
- variant: PropTypes.oneOf(['tabs', 'pills']),
52
+ variant: PropTypes.oneOf(['pills', 'tabs', 'underline']),
53
53
  }
54
54
 
55
55
  CNav.displayName = 'CNav'
@@ -36,6 +36,13 @@ export interface CNavGroupProps {
36
36
  idx?: string
37
37
  }
38
38
 
39
+ const isInVisibleGroup = (el1: string, el2: string) => {
40
+ const array1 = el1.toString().split('.')
41
+ const array2 = el2.toString().split('.')
42
+
43
+ return array2.every((item, index) => item === array1[index])
44
+ }
45
+
39
46
  export const CNavGroup = forwardRef<HTMLLIElement, CNavGroupProps>(
40
47
  ({ children, className, compact, idx, toggler, visible, ...rest }, ref) => {
41
48
  const [height, setHeight] = useState<number | string>()
@@ -45,12 +52,12 @@ export const CNavGroup = forwardRef<HTMLLIElement, CNavGroupProps>(
45
52
 
46
53
  const [_visible, setVisible] = useState(
47
54
  Boolean(
48
- visible || (idx && visibleGroup && visibleGroup.toString().startsWith(idx.toString())),
55
+ visible || (idx && visibleGroup && isInVisibleGroup(visibleGroup, idx)),
49
56
  ),
50
57
  )
51
58
 
52
59
  useEffect(() => {
53
- setVisible(Boolean(idx && visibleGroup && visibleGroup.toString().startsWith(idx.toString())))
60
+ setVisible(Boolean(idx && visibleGroup && isInVisibleGroup(visibleGroup, idx)))
54
61
  }, [visibleGroup])
55
62
 
56
63
  const handleTogglerOnCLick = (event: React.MouseEvent<HTMLElement>) => {
@@ -41,13 +41,14 @@ exports[`CNav example 1`] = `
41
41
  <li
42
42
  class="nav-item dropdown"
43
43
  >
44
- <a
44
+ <button
45
45
  aria-expanded="false"
46
- class="dropdown-toggle nav-link"
47
- href="#"
46
+ class="btn btn-primary dropdown-toggle"
47
+ tabindex="0"
48
+ type="button"
48
49
  >
49
50
  A
50
- </a>
51
+ </button>
51
52
  <ul
52
53
  aria-hidden="true"
53
54
  class="dropdown-menu"
@@ -17,7 +17,7 @@ export interface CNavbarProps extends HTMLAttributes<HTMLDivElement> {
17
17
  */
18
18
  color?: Colors
19
19
  /**
20
- * Sets if the color of text should be colored for a light or dark dark background.
20
+ * Sets if the color of text should be colored for a light or dark background.
21
21
  */
22
22
  colorScheme?: 'dark' | 'light'
23
23
  /**
@@ -59,12 +59,12 @@ export const CNavbar = forwardRef<HTMLDivElement, CNavbarProps>(
59
59
  'navbar',
60
60
  {
61
61
  [`bg-${color}`]: color,
62
- [`navbar-${colorScheme}`]: colorScheme,
63
62
  [typeof expand === 'boolean' ? 'navbar-expand' : `navbar-expand-${expand}`]: expand,
64
63
  },
65
64
  placement,
66
65
  className,
67
66
  )}
67
+ {...(colorScheme && { 'data-coreui-theme': colorScheme })}
68
68
  {...rest}
69
69
  ref={ref}
70
70
  >
@@ -26,9 +26,9 @@ test('CNavbar customize', async () => {
26
26
  expect(container.firstChild).toHaveClass('bazinga')
27
27
  expect(container.firstChild).toHaveClass('navbar')
28
28
  expect(container.firstChild).toHaveClass('bg-warning')
29
- expect(container.firstChild).toHaveClass('navbar-dark')
30
29
  expect(container.firstChild).toHaveClass('navbar-expand-lg')
31
30
  expect(container.firstChild).toHaveClass('fixed-bottom')
31
+ expect(container.firstChild).toHaveAttribute('data-coreui-theme', 'dark')
32
32
  const arrLength = container.getElementsByClassName('container-xl').length
33
33
  expect(arrLength).toBe(1)
34
34
  })
@@ -17,7 +17,8 @@ exports[`CNavbar customize - container and expand are boolean 1`] = `
17
17
  exports[`CNavbar customize 1`] = `
18
18
  <div>
19
19
  <h3
20
- class="navbar bg-warning navbar-dark navbar-expand-lg fixed-bottom bazinga"
20
+ class="navbar bg-warning navbar-expand-lg fixed-bottom bazinga"
21
+ data-coreui-theme="dark"
21
22
  >
22
23
  <div
23
24
  class="container-xl"
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
  import { Transition } from 'react-transition-group'
5
5
 
6
- import { CBackdrop } from '../backdrop/CBackdrop'
6
+ import { CBackdrop } from '../backdrop'
7
7
  import { CConditionalPortal } from '../conditional-portal'
8
8
 
9
9
  import { useForkedRef } from '../../hooks'
@@ -17,6 +17,10 @@ export interface COffcanvasProps extends HTMLAttributes<HTMLDivElement> {
17
17
  * A string of all className you want applied to the base component.
18
18
  */
19
19
  className?: string
20
+ /**
21
+ * Sets a darker color scheme.
22
+ */
23
+ dark?: boolean
20
24
  /**
21
25
  * Closes the offcanvas when escape key is pressed.
22
26
  */
@@ -59,6 +63,7 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
59
63
  children,
60
64
  backdrop = true,
61
65
  className,
66
+ dark,
62
67
  keyboard = true,
63
68
  onHide,
64
69
  onShow,
@@ -135,6 +140,7 @@ export const COffcanvas = forwardRef<HTMLDivElement, COffcanvasProps>(
135
140
  role="dialog"
136
141
  tabIndex={-1}
137
142
  onKeyDown={handleKeyDown}
143
+ {...(dark && { 'data-coreui-theme': 'dark' })}
138
144
  {...rest}
139
145
  ref={forkedRef}
140
146
  >
@@ -161,6 +167,7 @@ COffcanvas.propTypes = {
161
167
  backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf<'static'>(['static'])]),
162
168
  children: PropTypes.node,
163
169
  className: PropTypes.string,
170
+ dark: PropTypes.bool,
164
171
  keyboard: PropTypes.bool,
165
172
  onHide: PropTypes.func,
166
173
  onShow: PropTypes.func,
@@ -7,7 +7,7 @@ import { Transition } from 'react-transition-group'
7
7
  import { usePopper } from '../../hooks'
8
8
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
9
  import type { Placements, Triggers } from '../../types'
10
- import { getRTLPlacement } from '../../utils'
10
+ import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
11
11
 
12
12
  export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'> {
13
13
  /**
@@ -164,7 +164,7 @@ export const CPopover: FC<CPopoverProps> = ({
164
164
  onExit={onHide}
165
165
  timeout={{
166
166
  enter: 0,
167
- exit: 200,
167
+ exit: popoverRef.current ? getTransitionDurationFromElement(popoverRef.current) + 50 : 200,
168
168
  }}
169
169
  unmountOnExit
170
170
  >