@coreui/react 4.1.2 → 4.2.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 (118) hide show
  1. package/README.md +1 -1
  2. package/dist/components/accordion/index.d.ts +7 -0
  3. package/dist/components/alert/index.d.ts +4 -0
  4. package/dist/components/avatar/index.d.ts +2 -0
  5. package/dist/components/backdrop/index.d.ts +2 -0
  6. package/dist/components/badge/index.d.ts +2 -0
  7. package/dist/components/breadcrumb/index.d.ts +3 -0
  8. package/dist/components/button/index.d.ts +2 -0
  9. package/dist/components/button-group/index.d.ts +3 -0
  10. package/dist/components/callout/index.d.ts +2 -0
  11. package/dist/components/card/index.d.ts +12 -0
  12. package/dist/components/carousel/index.d.ts +4 -0
  13. package/dist/components/close-button/index.d.ts +2 -0
  14. package/dist/components/collapse/index.d.ts +2 -0
  15. package/dist/components/dropdown/CDropdown.d.ts +10 -1
  16. package/dist/components/dropdown/CDropdownToggle.d.ts +4 -0
  17. package/dist/components/dropdown/index.d.ts +8 -0
  18. package/dist/components/footer/index.d.ts +2 -0
  19. package/dist/components/form/CForm.d.ts +2 -2
  20. package/dist/components/form/CFormCheck.d.ts +2 -1
  21. package/dist/components/form/CFormControlValidation.d.ts +46 -0
  22. package/dist/components/form/CFormControlWrapper.d.ts +31 -0
  23. package/dist/components/form/CFormInput.d.ts +6 -9
  24. package/dist/components/form/CFormRange.d.ts +7 -1
  25. package/dist/components/form/CFormSelect.d.ts +3 -10
  26. package/dist/components/form/CFormTextarea.d.ts +2 -9
  27. package/dist/components/form/index.d.ts +14 -0
  28. package/dist/components/grid/index.d.ts +4 -0
  29. package/dist/components/header/index.d.ts +7 -0
  30. package/dist/components/image/index.d.ts +2 -0
  31. package/dist/components/index.d.ts +35 -0
  32. package/dist/components/link/index.d.ts +2 -0
  33. package/dist/components/list-group/index.d.ts +3 -0
  34. package/dist/components/modal/CModal.d.ts +3 -0
  35. package/dist/components/modal/index.d.ts +8 -0
  36. package/dist/components/nav/index.d.ts +7 -0
  37. package/dist/components/navbar/index.d.ts +6 -0
  38. package/dist/components/offcanvas/index.d.ts +5 -0
  39. package/dist/components/pagination/index.d.ts +3 -0
  40. package/dist/components/placeholder/index.d.ts +2 -0
  41. package/dist/components/popover/CPopover.d.ts +2 -2
  42. package/dist/components/popover/index.d.ts +2 -0
  43. package/dist/components/progress/index.d.ts +3 -0
  44. package/dist/components/sidebar/index.d.ts +7 -0
  45. package/dist/components/spinner/index.d.ts +2 -0
  46. package/dist/components/table/index.d.ts +9 -0
  47. package/dist/components/tabs/index.d.ts +3 -0
  48. package/dist/components/toast/index.d.ts +6 -0
  49. package/dist/components/tooltip/CTooltip.d.ts +6 -2
  50. package/dist/components/tooltip/index.d.ts +2 -0
  51. package/dist/index.d.ts +0 -120
  52. package/dist/index.es.js +585 -512
  53. package/dist/index.es.js.map +1 -1
  54. package/dist/index.js +585 -512
  55. package/dist/index.js.map +1 -1
  56. package/package.json +13 -12
  57. package/src/components/accordion/CAccordionButton.tsx +2 -1
  58. package/src/components/accordion/__tests__/__snapshots__/CAccordionButton.spec.tsx.snap +2 -0
  59. package/src/components/accordion/__tests__/__snapshots__/CAccordionHeader.spec.tsx.snap +2 -0
  60. package/src/components/accordion/index.ts +15 -0
  61. package/src/components/alert/CAlert.tsx +14 -3
  62. package/src/components/alert/index.ts +5 -0
  63. package/src/components/avatar/index.ts +3 -0
  64. package/src/components/backdrop/CBackdrop.tsx +10 -3
  65. package/src/components/backdrop/index.ts +3 -0
  66. package/src/components/badge/index.ts +3 -0
  67. package/src/components/breadcrumb/index.ts +4 -0
  68. package/src/components/button/index.ts +3 -0
  69. package/src/components/button-group/index.ts +4 -0
  70. package/src/components/callout/index.ts +3 -0
  71. package/src/components/card/index.ts +25 -0
  72. package/src/components/carousel/index.ts +5 -0
  73. package/src/components/close-button/index.ts +3 -0
  74. package/src/components/collapse/CCollapse.tsx +1 -0
  75. package/src/components/collapse/index.ts +3 -0
  76. package/src/components/dropdown/CDropdown.tsx +19 -24
  77. package/src/components/dropdown/CDropdownItem.tsx +1 -1
  78. package/src/components/dropdown/CDropdownMenu.tsx +55 -3
  79. package/src/components/dropdown/CDropdownToggle.tsx +28 -5
  80. package/src/components/dropdown/index.ts +17 -0
  81. package/src/components/footer/index.ts +3 -0
  82. package/src/components/form/CForm.tsx +2 -2
  83. package/src/components/form/CFormCheck.tsx +32 -7
  84. package/src/components/form/CFormControlValidation.tsx +97 -0
  85. package/src/components/form/CFormControlWrapper.tsx +85 -0
  86. package/src/components/form/CFormInput.tsx +75 -19
  87. package/src/components/form/CFormRange.tsx +18 -4
  88. package/src/components/form/CFormSelect.tsx +60 -32
  89. package/src/components/form/CFormTextarea.tsx +45 -17
  90. package/src/components/form/index.ts +29 -0
  91. package/src/components/grid/index.ts +5 -0
  92. package/src/components/header/index.ts +8 -0
  93. package/src/components/image/index.ts +3 -0
  94. package/src/components/index.ts +35 -0
  95. package/src/components/link/index.ts +3 -0
  96. package/src/components/list-group/index.ts +4 -0
  97. package/src/components/modal/CModal.tsx +3 -2
  98. package/src/components/modal/index.ts +9 -0
  99. package/src/components/nav/CNavGroup.tsx +1 -0
  100. package/src/components/nav/index.ts +8 -0
  101. package/src/components/navbar/index.ts +7 -0
  102. package/src/components/offcanvas/COffcanvas.tsx +1 -0
  103. package/src/components/offcanvas/index.ts +6 -0
  104. package/src/components/pagination/index.ts +4 -0
  105. package/src/components/placeholder/index.ts +3 -0
  106. package/src/components/popover/CPopover.tsx +59 -57
  107. package/src/components/popover/index.ts +3 -0
  108. package/src/components/progress/index.ts +4 -0
  109. package/src/components/sidebar/index.ts +8 -0
  110. package/src/components/spinner/index.ts +3 -0
  111. package/src/components/table/index.ts +19 -0
  112. package/src/components/tabs/CTabPane.tsx +7 -3
  113. package/src/components/tabs/index.ts +4 -0
  114. package/src/components/toast/CToast.tsx +6 -1
  115. package/src/components/toast/index.ts +7 -0
  116. package/src/components/tooltip/CTooltip.tsx +63 -45
  117. package/src/components/tooltip/index.ts +3 -0
  118. package/src/index.ts +0 -242
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreui/react",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
4
4
  "description": "UI Components Library for React.js",
5
5
  "keywords": [
6
6
  "react",
@@ -35,24 +35,25 @@
35
35
  "build": "rollup -c"
36
36
  },
37
37
  "devDependencies": {
38
- "@popperjs/core": "^2.11.0",
39
- "@rollup/plugin-commonjs": "^21.0.1",
40
- "@rollup/plugin-node-resolve": "^13.1.1",
41
- "@rollup/plugin-typescript": "^8.3.0",
42
- "@testing-library/jest-dom": "^5.16.1",
43
- "@testing-library/react": "^12.1.2",
44
- "@types/react": "^17.0.38",
45
- "@types/react-dom": "^17.0.11",
38
+ "@popperjs/core": "^2.11.5",
39
+ "@rollup/plugin-commonjs": "^21.0.3",
40
+ "@rollup/plugin-node-resolve": "^13.1.3",
41
+ "@rollup/plugin-typescript": "^8.3.1",
42
+ "@testing-library/jest-dom": "^5.16.4",
43
+ "@testing-library/react": "^12.1.4",
44
+ "@types/react": "17.0.39",
45
+ "@types/react-dom": "^17.0.15",
46
46
  "@types/react-transition-group": "^4.4.4",
47
47
  "classnames": "^2.3.1",
48
- "prop-types": "^15.8.0",
48
+ "prop-types": "^15.8.1",
49
49
  "react": "^17.0.2",
50
50
  "react-dom": "^17.0.2",
51
51
  "react-popper": "^2.2.5",
52
52
  "react-transition-group": "^4.4.2",
53
- "rollup": "^2.62.0",
53
+ "rollup": "^2.70.1",
54
54
  "rollup-plugin-peer-deps-external": "^2.2.4",
55
- "typescript": "^4.5.4"
55
+ "tslib": "^2.4.0",
56
+ "typescript": "^4.6.3"
56
57
  },
57
58
  "peerDependencies": {
58
59
  "react": "^17",
@@ -19,10 +19,11 @@ export const CAccordionButton = forwardRef<HTMLButtonElement, CAccordionButtonPr
19
19
 
20
20
  return (
21
21
  <button
22
+ type="button"
22
23
  className={_className}
23
- {...rest}
24
24
  aria-expanded={!visible}
25
25
  onClick={() => setVisible(!visible)}
26
+ {...rest}
26
27
  ref={ref}
27
28
  >
28
29
  {children}
@@ -5,6 +5,7 @@ exports[`CAccordionButton customize 1`] = `
5
5
  <button
6
6
  aria-expanded="true"
7
7
  class="accordion-button collapsed bazinga"
8
+ type="button"
8
9
  >
9
10
  Test
10
11
  </button>
@@ -16,6 +17,7 @@ exports[`loads and displays CAccordionButton component 1`] = `
16
17
  <button
17
18
  aria-expanded="true"
18
19
  class="accordion-button collapsed"
20
+ type="button"
19
21
  >
20
22
  Test
21
23
  </button>
@@ -8,6 +8,7 @@ exports[`CAccordionHeader customize 1`] = `
8
8
  <button
9
9
  aria-expanded="true"
10
10
  class="accordion-button collapsed"
11
+ type="button"
11
12
  >
12
13
  Test
13
14
  </button>
@@ -23,6 +24,7 @@ exports[`loads and displays CAccordionHeader component 1`] = `
23
24
  <button
24
25
  aria-expanded="true"
25
26
  class="accordion-button collapsed"
27
+ type="button"
26
28
  >
27
29
  Test
28
30
  </button>
@@ -0,0 +1,15 @@
1
+ import { CAccordion } from './CAccordion'
2
+ import { CAccordionBody } from './CAccordionBody'
3
+ import { CAccordionButton } from './CAccordionButton'
4
+ import { CAccordionCollapse } from './CAccordionCollapse'
5
+ import { CAccordionHeader } from './CAccordionHeader'
6
+ import { CAccordionItem } from './CAccordionItem'
7
+
8
+ export {
9
+ CAccordion,
10
+ CAccordionBody,
11
+ CAccordionButton,
12
+ CAccordionCollapse,
13
+ CAccordionHeader,
14
+ CAccordionItem,
15
+ }
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, HTMLAttributes, useEffect, useState } from 'react'
1
+ import React, { forwardRef, HTMLAttributes, useEffect, useState, useRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
  import { Transition } from 'react-transition-group'
@@ -6,6 +6,8 @@ import { Transition } from 'react-transition-group'
6
6
  import { Colors, colorPropType } from '../Types'
7
7
  import { CCloseButton } from '../close-button/CCloseButton'
8
8
 
9
+ import { useForkedRef } from '../../utils/hooks'
10
+
9
11
  export interface CAlertProps extends HTMLAttributes<HTMLDivElement> {
10
12
  /**
11
13
  * A string of all className you want applied to the component.
@@ -49,6 +51,8 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
49
51
  },
50
52
  ref,
51
53
  ) => {
54
+ const alertRef = useRef<HTMLDivElement>(null)
55
+ const forkedRef = useForkedRef(ref, alertRef)
52
56
  const [_visible, setVisible] = useState(visible)
53
57
 
54
58
  useEffect(() => {
@@ -69,7 +73,14 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
69
73
  }
70
74
 
71
75
  return (
72
- <Transition in={_visible} mountOnEnter onExit={onClose} timeout={150} unmountOnExit>
76
+ <Transition
77
+ in={_visible}
78
+ mountOnEnter
79
+ nodeRef={alertRef}
80
+ onExit={onClose}
81
+ timeout={150}
82
+ unmountOnExit
83
+ >
73
84
  {(state) => {
74
85
  const transitionClass = getTransitionClass(state)
75
86
  return (
@@ -77,7 +88,7 @@ export const CAlert = forwardRef<HTMLDivElement, CAlertProps>(
77
88
  className={classNames(_className, transitionClass)}
78
89
  role="alert"
79
90
  {...rest}
80
- ref={ref}
91
+ ref={forkedRef}
81
92
  >
82
93
  {children}
83
94
  {dismissible && <CCloseButton onClick={() => setVisible(false)} />}
@@ -0,0 +1,5 @@
1
+ import { CAlert } from './CAlert'
2
+ import { CAlertHeading } from './CAlertHeading'
3
+ import { CAlertLink } from './CAlertLink'
4
+
5
+ export { CAlert, CAlertHeading, CAlertLink }
@@ -0,0 +1,3 @@
1
+ import { CAvatar } from './CAvatar'
2
+
3
+ export { CAvatar }
@@ -1,8 +1,10 @@
1
- import React, { forwardRef, HTMLAttributes } from 'react'
1
+ import React, { forwardRef, HTMLAttributes, useRef } from 'react'
2
2
  import { Transition } from 'react-transition-group'
3
3
  import PropTypes from 'prop-types'
4
4
  import classNames from 'classnames'
5
5
 
6
+ import { useForkedRef } from '../../utils/hooks'
7
+
6
8
  export interface CBackdropProps extends HTMLAttributes<HTMLDivElement> {
7
9
  /**
8
10
  * A string of all className you want applied to the base component.
@@ -16,6 +18,9 @@ export interface CBackdropProps extends HTMLAttributes<HTMLDivElement> {
16
18
 
17
19
  export const CBackdrop = forwardRef<HTMLDivElement, CBackdropProps>(
18
20
  ({ className = 'modal-backdrop', visible, ...rest }, ref) => {
21
+ const backdropRef = useRef<HTMLDivElement>(null)
22
+ const forkedRef = useForkedRef(ref, backdropRef)
23
+
19
24
  const _className = classNames(className, 'fade')
20
25
 
21
26
  const getTransitionClass = (state: string) => {
@@ -23,10 +28,12 @@ export const CBackdrop = forwardRef<HTMLDivElement, CBackdropProps>(
23
28
  }
24
29
 
25
30
  return (
26
- <Transition in={visible} mountOnEnter timeout={150} unmountOnExit>
31
+ <Transition in={visible} mountOnEnter nodeRef={backdropRef} timeout={150} unmountOnExit>
27
32
  {(state) => {
28
33
  const transitionClass = getTransitionClass(state)
29
- return <div className={classNames(_className, transitionClass)} {...rest} ref={ref} />
34
+ return (
35
+ <div className={classNames(_className, transitionClass)} {...rest} ref={forkedRef} />
36
+ )
30
37
  }}
31
38
  </Transition>
32
39
  )
@@ -0,0 +1,3 @@
1
+ import { CBackdrop } from './CBackdrop'
2
+
3
+ export { CBackdrop }
@@ -0,0 +1,3 @@
1
+ import { CBadge } from './CBadge'
2
+
3
+ export { CBadge }
@@ -0,0 +1,4 @@
1
+ import { CBreadcrumb } from './CBreadcrumb'
2
+ import { CBreadcrumbItem } from './CBreadcrumbItem'
3
+
4
+ export { CBreadcrumb, CBreadcrumbItem }
@@ -0,0 +1,3 @@
1
+ import { CButton } from './CButton'
2
+
3
+ export { CButton }
@@ -0,0 +1,4 @@
1
+ import { CButtonToolbar } from './CButtonToolbar'
2
+ import { CButtonGroup } from './CButtonGroup'
3
+
4
+ export { CButtonToolbar, CButtonGroup }
@@ -0,0 +1,3 @@
1
+ import { CCallout } from './CCallout'
2
+
3
+ export { CCallout }
@@ -0,0 +1,25 @@
1
+ import { CCard } from './CCard'
2
+ import { CCardBody } from './CCardBody'
3
+ import { CCardFooter } from './CCardFooter'
4
+ import { CCardGroup } from './CCardGroup'
5
+ import { CCardHeader } from './CCardHeader'
6
+ import { CCardImage } from './CCardImage'
7
+ import { CCardImageOverlay } from './CCardImageOverlay'
8
+ import { CCardLink } from './CCardLink'
9
+ import { CCardSubtitle } from './CCardSubtitle'
10
+ import { CCardText } from './CCardText'
11
+ import { CCardTitle } from './CCardTitle'
12
+
13
+ export {
14
+ CCard,
15
+ CCardBody,
16
+ CCardFooter,
17
+ CCardGroup,
18
+ CCardHeader,
19
+ CCardImage,
20
+ CCardImageOverlay,
21
+ CCardLink,
22
+ CCardSubtitle,
23
+ CCardText,
24
+ CCardTitle,
25
+ }
@@ -0,0 +1,5 @@
1
+ import { CCarousel } from './CCarousel'
2
+ import { CCarouselCaption } from './CCarouselCaption'
3
+ import { CCarouselItem } from './CCarouselItem'
4
+
5
+ export { CCarousel, CCarouselCaption, CCarouselItem }
@@ -0,0 +1,3 @@
1
+ import { CCloseButton } from './CCloseButton'
2
+
3
+ export { CCloseButton }
@@ -98,6 +98,7 @@ export const CCollapse = forwardRef<HTMLDivElement, CCollapseProps>(
98
98
  return (
99
99
  <CSSTransition
100
100
  in={visible}
101
+ nodeRef={collapseRef}
101
102
  onEntering={onEntering}
102
103
  onEntered={onEntered}
103
104
  onExit={onExit}
@@ -0,0 +1,3 @@
1
+ import { CCollapse } from './CCollapse'
2
+
3
+ export { CCollapse }
@@ -3,6 +3,7 @@ import React, {
3
3
  ElementType,
4
4
  forwardRef,
5
5
  HTMLAttributes,
6
+ RefObject,
6
7
  useEffect,
7
8
  useRef,
8
9
  useState,
@@ -33,6 +34,14 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
33
34
  * @type 'start' | 'end' | { xs: 'start' | 'end' } | { sm: 'start' | 'end' } | { md: 'start' | 'end' } | { lg: 'start' | 'end' } | { xl: 'start' | 'end'} | { xxl: 'start' | 'end'}
34
35
  */
35
36
  alignment?: Alignments
37
+ /**
38
+ * Configure the auto close behavior of the dropdown:
39
+ * - `true` - the dropdown will be closed by clicking outside or inside the dropdown menu.
40
+ * - `false` - the dropdown will be closed by clicking the toggle button and manually calling hide or toggle method. (Also will not be closed by pressing esc key)
41
+ * - `'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.
42
+ * - `'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.
43
+ */
44
+ autoClose?: 'inside' | 'outside' | boolean
36
45
  /**
37
46
  * A string of all className you want applied to the base component.
38
47
  */
@@ -80,6 +89,8 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
80
89
  }
81
90
 
82
91
  interface ContextProps extends CDropdownProps {
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ dropdownToggleRef: RefObject<any> | undefined
83
94
  setVisible: React.Dispatch<React.SetStateAction<boolean | undefined>>
84
95
  }
85
96
 
@@ -90,6 +101,7 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
90
101
  {
91
102
  children,
92
103
  alignment,
104
+ autoClose = true,
93
105
  className,
94
106
  dark,
95
107
  direction,
@@ -106,6 +118,7 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
106
118
  ) => {
107
119
  const [_visible, setVisible] = useState(visible)
108
120
  const dropdownRef = useRef<HTMLDivElement>(null)
121
+ const dropdownToggleRef = useRef(null)
109
122
  const forkedRef = useForkedRef(ref, dropdownRef)
110
123
 
111
124
  const Component = variant === 'nav-item' ? 'li' : component
@@ -117,8 +130,10 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
117
130
 
118
131
  const contextValues = {
119
132
  alignment,
133
+ autoClose,
120
134
  dark,
121
135
  direction: direction,
136
+ dropdownToggleRef,
122
137
  placement: placement,
123
138
  popper,
124
139
  variant,
@@ -135,19 +150,6 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
135
150
  className,
136
151
  )
137
152
 
138
- useEffect(() => {
139
- _visible &&
140
- setTimeout(() => {
141
- window.addEventListener('click', handleClickOutside)
142
- window.addEventListener('keyup', handleKeyup)
143
- })
144
-
145
- return () => {
146
- window.removeEventListener('click', handleClickOutside)
147
- window.removeEventListener('keyup', handleKeyup)
148
- }
149
- }, [_visible])
150
-
151
153
  useEffect(() => {
152
154
  setVisible(visible)
153
155
  }, [visible])
@@ -157,17 +159,6 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
157
159
  !_visible && onHide && onHide()
158
160
  }, [_visible])
159
161
 
160
- const handleKeyup = (event: Event) => {
161
- if (!dropdownRef.current?.contains(event.target as HTMLElement)) {
162
- setVisible(false)
163
- }
164
- }
165
- const handleClickOutside = (event: Event) => {
166
- if (!dropdownRef.current?.contains(event.target as HTMLElement)) {
167
- setVisible(false)
168
- }
169
- }
170
-
171
162
  const dropdownContent = () => {
172
163
  return variant === 'input-group' ? (
173
164
  <>{children}</>
@@ -203,6 +194,10 @@ CDropdown.propTypes = {
203
194
  PropTypes.shape({ xl: alignmentDirection }),
204
195
  PropTypes.shape({ xxl: alignmentDirection }),
205
196
  ]),
197
+ autoClose: PropTypes.oneOfType([
198
+ PropTypes.bool,
199
+ PropTypes.oneOf<'inside' | 'outside'>(['inside', 'outside']),
200
+ ]),
206
201
  children: PropTypes.node,
207
202
  className: PropTypes.string,
208
203
  component: PropTypes.elementType,
@@ -21,7 +21,7 @@ export const CDropdownItem = forwardRef<HTMLButtonElement | HTMLAnchorElement, C
21
21
  const _className = classNames('dropdown-item', className)
22
22
 
23
23
  return (
24
- <CLink component={component} {...rest} className={_className} ref={ref}>
24
+ <CLink className={_className} component={component} {...rest} ref={ref}>
25
25
  {children}
26
26
  </CLink>
27
27
  )
@@ -1,5 +1,5 @@
1
+ import React, { ElementType, FC, HTMLAttributes, useContext, useEffect, useRef } from 'react'
1
2
  import PropTypes from 'prop-types'
2
- import React, { ElementType, FC, HTMLAttributes, useContext } from 'react'
3
3
  import classNames from 'classnames'
4
4
  import { Popper, PopperChildrenProps } from 'react-popper'
5
5
 
@@ -35,19 +35,69 @@ export const CDropdownMenu: FC<CDropdownMenuProps> = ({
35
35
  component: Component = 'ul',
36
36
  ...rest
37
37
  }) => {
38
- const { alignment, dark, direction, placement, popper, visible } = useContext(CDropdownContext)
38
+ const {
39
+ alignment,
40
+ autoClose,
41
+ dark,
42
+ direction,
43
+ dropdownToggleRef,
44
+ placement,
45
+ popper,
46
+ visible,
47
+ setVisible,
48
+ } = useContext(CDropdownContext)
49
+
50
+ const dropdownMenuRef = useRef<HTMLDivElement>(null)
51
+
52
+ useEffect(() => {
53
+ visible && window.addEventListener('mouseup', handleMouseUp)
54
+ visible && window.addEventListener('keyup', handleKeyup)
55
+
56
+ return () => {
57
+ window.removeEventListener('mouseup', handleMouseUp)
58
+ window.removeEventListener('keyup', handleKeyup)
59
+ }
60
+ }, [visible])
61
+
62
+ const handleKeyup = (event: KeyboardEvent) => {
63
+ if (autoClose === false) {
64
+ return
65
+ }
66
+
67
+ if (event.key === 'Escape') {
68
+ setVisible(false)
69
+ }
70
+ }
71
+
72
+ const handleMouseUp = (event: Event) => {
73
+ if (dropdownToggleRef && dropdownToggleRef.current.contains(event.target as HTMLElement)) {
74
+ return
75
+ }
76
+
77
+ if (
78
+ autoClose === true ||
79
+ (autoClose === 'inside' && dropdownMenuRef.current?.contains(event.target as HTMLElement)) ||
80
+ (autoClose === 'outside' && !dropdownMenuRef.current?.contains(event.target as HTMLElement))
81
+ ) {
82
+ setTimeout(() => setVisible(false), 1)
83
+ return
84
+ }
85
+ }
39
86
 
40
87
  let _placement: Placements = placement
41
88
 
42
89
  if (direction === 'dropup') {
43
90
  _placement = 'top-start'
44
91
  }
92
+
45
93
  if (direction === 'dropend') {
46
94
  _placement = 'right-start'
47
95
  }
96
+
48
97
  if (direction === 'dropstart') {
49
98
  _placement = 'left-start'
50
99
  }
100
+
51
101
  if (alignment === 'end') {
52
102
  _placement = 'bottom-end'
53
103
  }
@@ -101,7 +151,9 @@ export const CDropdownMenu: FC<CDropdownMenuProps> = ({
101
151
  }
102
152
 
103
153
  return popper && visible ? (
104
- <Popper placement={_placement}>{({ ref, style }) => dropdownMenuComponent(style, ref)}</Popper>
154
+ <Popper innerRef={dropdownMenuRef} placement={_placement}>
155
+ {({ ref, style }) => dropdownMenuComponent(style, ref)}
156
+ </Popper>
105
157
  ) : (
106
158
  dropdownMenuComponent()
107
159
  )
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
  import { Reference } from 'react-popper'
5
5
 
6
+ import { useForkedRef } from '../../utils/hooks'
7
+
6
8
  import { Triggers, triggerPropType } from '../Types'
7
9
 
8
10
  import { CButton, CButtonProps } from '../button/CButton'
@@ -13,6 +15,10 @@ export interface CDropdownToggleProps extends Omit<CButtonProps, 'type'> {
13
15
  * Enables pseudo element caret on toggler.
14
16
  */
15
17
  caret?: boolean
18
+ /**
19
+ * Create a custom toggler which accepts any content.
20
+ */
21
+ custom?: boolean
16
22
  /**
17
23
  * Similarly, create split button dropdowns with virtually the same markup as single button dropdowns, but with the addition of `.dropdown-toggle-split` className for proper spacing around the dropdown caret.
18
24
  */
@@ -28,12 +34,13 @@ export interface CDropdownToggleProps extends Omit<CButtonProps, 'type'> {
28
34
  export const CDropdownToggle: FC<CDropdownToggleProps> = ({
29
35
  children,
30
36
  caret = true,
37
+ custom,
31
38
  className,
32
39
  split,
33
40
  trigger = 'click',
34
41
  ...rest
35
42
  }) => {
36
- const { popper, variant, visible, setVisible } = useContext(CDropdownContext)
43
+ const { dropdownToggleRef, popper, variant, visible, setVisible } = useContext(CDropdownContext)
37
44
  const _className = classNames(
38
45
  {
39
46
  'dropdown-toggle': caret,
@@ -59,31 +66,47 @@ export const CDropdownToggle: FC<CDropdownToggleProps> = ({
59
66
  const togglerProps = {
60
67
  className: _className,
61
68
  'aria-expanded': visible,
69
+ ...(!rest.disabled && { ...triggers }),
62
70
  ...triggers,
63
71
  }
64
72
 
65
73
  // We use any because Toggler can be `a` as well as `button`.
66
74
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
75
  const Toggler = (ref?: React.Ref<any>) => {
68
- return variant === 'nav-item' ? (
69
- <a href="#" {...togglerProps} ref={ref}>
76
+ return custom && React.isValidElement(children) ? (
77
+ <>
78
+ {React.cloneElement(children, {
79
+ 'aria-expanded': visible,
80
+ ...(!rest.disabled && { ...triggers }),
81
+ ref: useForkedRef(ref, dropdownToggleRef),
82
+ })}
83
+ </>
84
+ ) : variant === 'nav-item' ? (
85
+ <a href="#" {...togglerProps} ref={useForkedRef(ref, dropdownToggleRef)}>
70
86
  {children}
71
87
  </a>
72
88
  ) : (
73
- <CButton type="button" {...togglerProps} tabIndex={0} {...rest} ref={ref}>
89
+ <CButton
90
+ type="button"
91
+ {...togglerProps}
92
+ tabIndex={0}
93
+ {...rest}
94
+ ref={useForkedRef(ref, dropdownToggleRef)}
95
+ >
74
96
  {children}
75
97
  {split && <span className="visually-hidden">Toggle Dropdown</span>}
76
98
  </CButton>
77
99
  )
78
100
  }
79
101
 
80
- return popper ? <Reference>{({ ref }) => Toggler(ref)}</Reference> : Toggler()
102
+ return popper ? <Reference>{({ ref }) => Toggler(ref)}</Reference> : Toggler(dropdownToggleRef)
81
103
  }
82
104
 
83
105
  CDropdownToggle.propTypes = {
84
106
  caret: PropTypes.bool,
85
107
  children: PropTypes.node,
86
108
  className: PropTypes.string,
109
+ custom: PropTypes.bool,
87
110
  split: PropTypes.bool,
88
111
  trigger: triggerPropType,
89
112
  }
@@ -0,0 +1,17 @@
1
+ import { CDropdown } from './CDropdown'
2
+ import { CDropdownDivider } from './CDropdownDivider'
3
+ import { CDropdownHeader } from './CDropdownHeader'
4
+ import { CDropdownItem } from './CDropdownItem'
5
+ import { CDropdownItemPlain } from './CDropdownItemPlain'
6
+ import { CDropdownMenu } from './CDropdownMenu'
7
+ import { CDropdownToggle } from './CDropdownToggle'
8
+
9
+ export {
10
+ CDropdown,
11
+ CDropdownDivider,
12
+ CDropdownHeader,
13
+ CDropdownItem,
14
+ CDropdownItemPlain,
15
+ CDropdownMenu,
16
+ CDropdownToggle,
17
+ }
@@ -0,0 +1,3 @@
1
+ import { CFooter } from './CFooter'
2
+
3
+ export { CFooter }
@@ -1,8 +1,8 @@
1
- import React, { forwardRef, HTMLAttributes } from 'react'
1
+ import React, { forwardRef, FormHTMLAttributes } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
 
5
- export interface CFormProps extends HTMLAttributes<HTMLFormElement> {
5
+ export interface CFormProps extends FormHTMLAttributes<HTMLFormElement> {
6
6
  /**
7
7
  * A string of all className you want applied to the component.
8
8
  */