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

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.
@@ -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.1",
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
  }
@@ -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) => {
@@ -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
  }
@@ -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'
@@ -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>) => {
@@ -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
  >
@@ -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
  >
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'
3
3
  import PropTypes from 'prop-types'
4
4
  import classNames from 'classnames'
5
5
 
6
- import { CBackdrop } from '../backdrop/CBackdrop'
6
+ import { CBackdrop } from '../backdrop'
7
7
 
8
8
  import { isInViewport } from '../../utils'
9
9
  import { useForkedRef } from '../../hooks'
@@ -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 CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {
13
13
  /**
@@ -158,7 +158,7 @@ export const CTooltip: FC<CTooltipProps> = ({
158
158
  onExit={onHide}
159
159
  timeout={{
160
160
  enter: 0,
161
- exit: 200,
161
+ exit: tooltipRef.current ? getTransitionDurationFromElement(tooltipRef.current) + 50 : 200,
162
162
  }}
163
163
  unmountOnExit
164
164
  >
@@ -2,6 +2,8 @@ import { useRef } from 'react'
2
2
  import { createPopper } from '@popperjs/core'
3
3
  import type { Instance, Options } from '@popperjs/core'
4
4
 
5
+ import { executeAfterTransition } from '../utils'
6
+
5
7
  interface UsePopperOutput {
6
8
  popper: Instance | undefined
7
9
  initPopper: (reference: HTMLElement, popper: HTMLElement, options: Partial<Options>) => void
@@ -10,14 +12,20 @@ interface UsePopperOutput {
10
12
 
11
13
  export const usePopper = (): UsePopperOutput => {
12
14
  const _popper = useRef<Instance>()
15
+ const el = useRef<HTMLElement>()
13
16
 
14
17
  const initPopper = (reference: HTMLElement, popper: HTMLElement, options: Partial<Options>) => {
15
18
  _popper.current = createPopper(reference, popper, options)
19
+ el.current = popper
16
20
  }
17
21
 
18
22
  const destroyPopper = () => {
19
- if (_popper.current) {
20
- _popper.current.destroy()
23
+ const popperInstance = _popper.current
24
+
25
+ if (popperInstance && el.current) {
26
+ executeAfterTransition(() => {
27
+ popperInstance.destroy()
28
+ }, el.current)
21
29
  }
22
30
 
23
31
  _popper.current = undefined
@@ -0,0 +1,46 @@
1
+ import getTransitionDurationFromElement from './getTransitionDurationFromElement'
2
+
3
+ const execute = (callback: () => void) => {
4
+ if (typeof callback === 'function') {
5
+ callback()
6
+ }
7
+ }
8
+
9
+ const triggerTransitionEnd = (element: HTMLElement) => {
10
+ element.dispatchEvent(new Event('transitionend'))
11
+ }
12
+
13
+ const executeAfterTransition = (
14
+ callback: () => void,
15
+ transitionElement: HTMLElement,
16
+ waitForTransition = true,
17
+ ) => {
18
+ if (!waitForTransition) {
19
+ execute(callback)
20
+ return
21
+ }
22
+
23
+ const durationPadding = 5
24
+ const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding
25
+
26
+ let called = false
27
+
28
+ const handler = ({ target }: { target: any }) => {
29
+ if (target !== transitionElement) {
30
+ return
31
+ }
32
+
33
+ called = true
34
+ transitionElement.removeEventListener('transitionend', handler)
35
+ execute(callback)
36
+ }
37
+
38
+ transitionElement.addEventListener('transitionend', handler)
39
+ setTimeout(() => {
40
+ if (!called) {
41
+ triggerTransitionEnd(transitionElement)
42
+ }
43
+ }, emulatedDuration)
44
+ }
45
+
46
+ export default executeAfterTransition
@@ -0,0 +1,24 @@
1
+ const getTransitionDurationFromElement = (element: HTMLElement) => {
2
+ if (!element) {
3
+ return 0
4
+ }
5
+
6
+ // Get transition-duration of the element
7
+ let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
8
+
9
+ const floatTransitionDuration = Number.parseFloat(transitionDuration)
10
+ const floatTransitionDelay = Number.parseFloat(transitionDelay)
11
+
12
+ // Return 0 if element or transition duration is not found
13
+ if (!floatTransitionDuration && !floatTransitionDelay) {
14
+ return 0
15
+ }
16
+
17
+ // If multiple durations are defined, take the first
18
+ transitionDuration = transitionDuration.split(',')[0]
19
+ transitionDelay = transitionDelay.split(',')[0]
20
+
21
+ return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * 1000
22
+ }
23
+
24
+ export default getTransitionDurationFromElement
@@ -1,5 +1,13 @@
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
6
 
5
- export { getRTLPlacement, isInViewport, isRTL }
7
+ export {
8
+ executeAfterTransition,
9
+ getRTLPlacement,
10
+ getTransitionDurationFromElement,
11
+ isInViewport,
12
+ isRTL,
13
+ }