@coreui/react-pro 5.24.0 → 5.25.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 (180) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/calendar/CCalendar.js +4 -3
  3. package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
  4. package/dist/cjs/components/date-picker/CDatePicker.d.ts +1 -1
  5. package/dist/cjs/components/date-picker/CDatePicker.js.map +1 -1
  6. package/dist/cjs/components/date-range-picker/CDateRangePicker.js +2 -0
  7. package/dist/cjs/components/date-range-picker/CDateRangePicker.js.map +1 -1
  8. package/dist/cjs/components/dropdown/CDropdown.js +1 -1
  9. package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
  10. package/dist/cjs/components/index.d.ts +1 -0
  11. package/dist/cjs/components/nav/CNavGroup.d.ts +3 -1
  12. package/dist/cjs/components/nav/CNavGroup.js +9 -5
  13. package/dist/cjs/components/nav/CNavGroup.js.map +1 -1
  14. package/dist/cjs/components/one-time-password-input/COneTimePassword.js +3 -3
  15. package/dist/cjs/components/one-time-password-input/COneTimePassword.js.map +1 -1
  16. package/dist/cjs/components/popover/CPopover.js +14 -17
  17. package/dist/cjs/components/popover/CPopover.js.map +1 -1
  18. package/dist/cjs/components/search-button/CSearchButton.d.ts +32 -0
  19. package/dist/cjs/components/search-button/CSearchButton.js +77 -0
  20. package/dist/cjs/components/search-button/CSearchButton.js.map +1 -0
  21. package/dist/cjs/components/search-button/index.d.ts +2 -0
  22. package/dist/cjs/components/search-button/types.d.ts +10 -0
  23. package/dist/cjs/components/search-button/utils.d.ts +11 -0
  24. package/dist/cjs/components/search-button/utils.js +115 -0
  25. package/dist/cjs/components/search-button/utils.js.map +1 -0
  26. package/dist/cjs/components/sidebar/CSidebarNav.d.ts +12 -0
  27. package/dist/cjs/components/sidebar/CSidebarNav.js +7 -2
  28. package/dist/cjs/components/sidebar/CSidebarNav.js.map +1 -1
  29. package/dist/cjs/components/smart-table/CSmartTable.js +2 -2
  30. package/dist/cjs/components/smart-table/CSmartTable.js.map +1 -1
  31. package/dist/cjs/components/smart-table/CSmartTableInterface.d.ts +2 -2
  32. package/dist/cjs/components/time-picker/CTimePicker.d.ts +1 -1
  33. package/dist/cjs/components/time-picker/CTimePicker.js +1 -1
  34. package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
  35. package/dist/cjs/components/tooltip/CTooltip.js +14 -17
  36. package/dist/cjs/components/tooltip/CTooltip.js.map +1 -1
  37. package/dist/cjs/components/virtual-scroller/CVirtualScroller.js +11 -8
  38. package/dist/cjs/components/virtual-scroller/CVirtualScroller.js.map +1 -1
  39. package/dist/cjs/hooks/useDebouncedCallback.d.ts +1 -1
  40. package/dist/cjs/hooks/useDebouncedCallback.js +1 -1
  41. package/dist/cjs/hooks/useDebouncedCallback.js.map +1 -1
  42. package/dist/cjs/index.js +2 -0
  43. package/dist/cjs/index.js.map +1 -1
  44. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js +1 -1
  45. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js.map +1 -1
  46. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js +1 -1
  47. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js.map +1 -1
  48. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js +1 -1
  49. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js.map +1 -1
  50. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js +1 -1
  51. package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js.map +1 -1
  52. package/dist/cjs/node_modules/ua-parser-js/dist/ua-parser.min.js +2 -2
  53. package/dist/cjs/node_modules/ua-parser-js/dist/ua-parser.min.js.map +1 -1
  54. package/dist/esm/components/calendar/CCalendar.js +4 -3
  55. package/dist/esm/components/calendar/CCalendar.js.map +1 -1
  56. package/dist/esm/components/date-picker/CDatePicker.d.ts +1 -1
  57. package/dist/esm/components/date-picker/CDatePicker.js.map +1 -1
  58. package/dist/esm/components/date-range-picker/CDateRangePicker.js +2 -0
  59. package/dist/esm/components/date-range-picker/CDateRangePicker.js.map +1 -1
  60. package/dist/esm/components/dropdown/CDropdown.js +1 -1
  61. package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
  62. package/dist/esm/components/index.d.ts +1 -0
  63. package/dist/esm/components/nav/CNavGroup.d.ts +3 -1
  64. package/dist/esm/components/nav/CNavGroup.js +9 -5
  65. package/dist/esm/components/nav/CNavGroup.js.map +1 -1
  66. package/dist/esm/components/one-time-password-input/COneTimePassword.js +3 -3
  67. package/dist/esm/components/one-time-password-input/COneTimePassword.js.map +1 -1
  68. package/dist/esm/components/popover/CPopover.js +14 -17
  69. package/dist/esm/components/popover/CPopover.js.map +1 -1
  70. package/dist/esm/components/search-button/CSearchButton.d.ts +32 -0
  71. package/dist/esm/components/search-button/CSearchButton.js +75 -0
  72. package/dist/esm/components/search-button/CSearchButton.js.map +1 -0
  73. package/dist/esm/components/search-button/index.d.ts +2 -0
  74. package/dist/esm/components/search-button/types.d.ts +10 -0
  75. package/dist/esm/components/search-button/utils.d.ts +11 -0
  76. package/dist/esm/components/search-button/utils.js +104 -0
  77. package/dist/esm/components/search-button/utils.js.map +1 -0
  78. package/dist/esm/components/sidebar/CSidebarNav.d.ts +12 -0
  79. package/dist/esm/components/sidebar/CSidebarNav.js +7 -2
  80. package/dist/esm/components/sidebar/CSidebarNav.js.map +1 -1
  81. package/dist/esm/components/smart-table/CSmartTable.js +2 -2
  82. package/dist/esm/components/smart-table/CSmartTable.js.map +1 -1
  83. package/dist/esm/components/smart-table/CSmartTableInterface.d.ts +2 -2
  84. package/dist/esm/components/time-picker/CTimePicker.d.ts +1 -1
  85. package/dist/esm/components/time-picker/CTimePicker.js +1 -1
  86. package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
  87. package/dist/esm/components/tooltip/CTooltip.js +14 -17
  88. package/dist/esm/components/tooltip/CTooltip.js.map +1 -1
  89. package/dist/esm/components/virtual-scroller/CVirtualScroller.js +11 -8
  90. package/dist/esm/components/virtual-scroller/CVirtualScroller.js.map +1 -1
  91. package/dist/esm/hooks/useDebouncedCallback.d.ts +1 -1
  92. package/dist/esm/hooks/useDebouncedCallback.js +1 -1
  93. package/dist/esm/hooks/useDebouncedCallback.js.map +1 -1
  94. package/dist/esm/index.js +1 -0
  95. package/dist/esm/index.js.map +1 -1
  96. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js +1 -1
  97. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js.map +1 -1
  98. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js +1 -1
  99. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js.map +1 -1
  100. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js +1 -1
  101. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js.map +1 -1
  102. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js +1 -1
  103. package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js.map +1 -1
  104. package/dist/esm/node_modules/ua-parser-js/dist/ua-parser.min.js +2 -2
  105. package/dist/esm/node_modules/ua-parser-js/dist/ua-parser.min.js.map +1 -1
  106. package/package.json +7 -5
  107. package/src/components/autocomplete/__tests__/CAutocomplete.spec.tsx +18 -16
  108. package/src/components/autocomplete/__tests__/CAutocompleteOptions.spec.tsx +37 -28
  109. package/src/components/autocomplete/__tests__/__snapshots__/CAutocomplete.spec.tsx.snap +129 -0
  110. package/src/components/autocomplete/__tests__/__snapshots__/CAutocompleteOptions.spec.tsx.snap +36 -0
  111. package/src/components/calendar/CCalendar.tsx +5 -4
  112. package/src/components/calendar/__tests__/CCalendar.spec.tsx +40 -45
  113. package/src/components/calendar/__tests__/__snapshots__/CCalendar.spec.tsx.snap +99 -75
  114. package/src/components/calendar/__tests__/utils.spec.tsx +8 -18
  115. package/src/components/date-picker/CDatePicker.tsx +12 -13
  116. package/src/components/date-picker/__tests__/CDatePicker.spec.tsx +1 -1
  117. package/src/components/date-picker/__tests__/__snapshots__/CDatePicker.spec.tsx.snap +12 -640
  118. package/src/components/date-range-picker/CDateRangePicker.tsx +2 -0
  119. package/src/components/date-range-picker/__tests__/CDateRangePicker.spec.tsx +1 -1
  120. package/src/components/date-range-picker/__tests__/__snapshots__/CDateRangePicker.spec.tsx.snap +21 -1259
  121. package/src/components/dropdown/CDropdown.tsx +1 -1
  122. package/src/components/dropdown/__tests__/CDropdown.spec.tsx +1 -1
  123. package/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap +2 -2
  124. package/src/components/dropdown/__tests__/__snapshots__/CDropdownMenu.spec.tsx.snap +1 -1
  125. package/src/components/form/__tests__/CFormInput.spec.tsx +17 -20
  126. package/src/components/form/__tests__/__snapshots__/CFormInput.spec.tsx.snap +12 -3
  127. package/src/components/index.ts +1 -0
  128. package/src/components/loading-button/__tests__/CLoadingButton.spec.tsx +1 -1
  129. package/src/components/loading-button/__tests__/__snapshots__/CLoadingButton.spec.tsx.snap +4 -4
  130. package/src/components/multi-select/__tests__/CMultiSelect.spec.tsx +15 -61
  131. package/src/components/multi-select/__tests__/CMultiSelectNativeSelect.spec.tsx +4 -5
  132. package/src/components/multi-select/__tests__/CMultiSelectOptions.spec.tsx +4 -4
  133. package/src/components/multi-select/__tests__/CMultiSelectSelection.spec.tsx +7 -24
  134. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelect.spec.tsx.snap +50 -20
  135. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectNativeSelect.spec.tsx.snap +0 -2
  136. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectOptions.spec.tsx.snap +6 -0
  137. package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectSelection.spec.tsx.snap +11 -8
  138. package/src/components/nav/CNavGroup.tsx +11 -6
  139. package/src/components/nav/__tests__/CNavGroup.spec.tsx +29 -1
  140. package/src/components/nav/__tests__/__snapshots__/CNavGroup.spec.tsx.snap +1 -1
  141. package/src/components/offcanvas/__tests__/COffcanvas.spec.tsx +1 -0
  142. package/src/components/one-time-password-input/COneTimePassword.tsx +10 -11
  143. package/src/components/picker/__tests__/CPicker.spec.tsx +1 -1
  144. package/src/components/picker/__tests__/__snapshots__/CPicker.spec.tsx.snap +2 -16
  145. package/src/components/popover/CPopover.tsx +15 -20
  146. package/src/components/popover/__tests__/CPopover.spec.tsx +10 -4
  147. package/src/components/search-button/CSearchButton.tsx +195 -0
  148. package/src/components/search-button/__tests__/CSearchButton.spec.tsx +95 -0
  149. package/src/components/search-button/__tests__/__snapshots__/CSearchButton.spec.tsx.snap +87 -0
  150. package/src/components/search-button/index.ts +3 -0
  151. package/src/components/search-button/types.ts +10 -0
  152. package/src/components/search-button/utils.ts +140 -0
  153. package/src/components/sidebar/CSidebarNav.tsx +27 -2
  154. package/src/components/smart-table/CSmartTable.tsx +5 -4
  155. package/src/components/smart-table/CSmartTableInterface.tsx +2 -2
  156. package/src/components/smart-table/__tests__/CSmartTable.spec.tsx +3 -2
  157. package/src/components/smart-table/__tests__/__snapshots__/CSmartTable.spec.tsx.snap +7 -15
  158. package/src/components/stepper/__tests__/CStepper.spec.tsx +24 -21
  159. package/src/components/stepper/__tests__/__snapshots__/CStepper.spec.tsx.snap +295 -0
  160. package/src/components/tabs/__tests__/CTabContent.spec.tsx +1 -0
  161. package/src/components/tabs/__tests__/CTabPane.spec.tsx +1 -0
  162. package/src/components/time-picker/CTimePicker.tsx +6 -7
  163. package/src/components/time-picker/__tests__/CTimePicker.spec.tsx +10 -1
  164. package/src/components/time-picker/__tests__/__snapshots__/CTimePicker.spec.tsx.snap +599 -315
  165. package/src/components/tooltip/CTooltip.tsx +15 -20
  166. package/src/components/tooltip/__tests__/CTooltip.spec.tsx +6 -0
  167. package/src/components/virtual-scroller/CVirtualScroller.tsx +13 -14
  168. package/src/hooks/useDebouncedCallback.ts +7 -4
  169. package/dist/cjs/components/accordion/CAccordionCollapse.d.ts +0 -3
  170. package/dist/esm/components/accordion/CAccordionCollapse.d.ts +0 -3
  171. package/src/components/accordion/CAccordionCollapse.tsx +0 -20
  172. package/src/components/accordion/__tests__/CAccordionCollapse.spec.tsx +0 -10
  173. package/src/components/accordion/__tests__/__snapshots__/CAccordionCollapse.spec.tsx.snap +0 -11
  174. package/src/components/button/__tests__/__snapshots__/CButtonClose.spec.tsx.snap +0 -26
  175. package/src/components/calendar/__tests__/CCalendar.accessibility.spec.tsx +0 -595
  176. package/src/components/calendar/__tests__/CCalendar.comprehensive.spec.tsx +0 -402
  177. package/src/components/calendar/__tests__/CCalendar.edge-cases.spec.tsx +0 -416
  178. package/src/components/calendar/__tests__/CCalendar.interactions.spec.tsx +0 -593
  179. package/src/components/header/CHeaderBrand.tsx~HEAD +0 -36
  180. /package/src/components/calendar/__tests__/{test-setup.ts → setup.ts} +0 -0
@@ -22,13 +22,8 @@ exports[`CMultiSelectSelection customize tags 1`] = `
22
22
  <button
23
23
  aria-label="Close"
24
24
  class="form-multi-select-tag-delete"
25
- >
26
- <span
27
- aria-hidden="true"
28
- >
29
- ×
30
- </span>
31
- </button>
25
+ type="button"
26
+ />
32
27
  </span>
33
28
  <span
34
29
  class="form-multi-select-tag"
@@ -44,7 +39,15 @@ exports[`CMultiSelectSelection customize text 1`] = `
44
39
  <span
45
40
  class="form-multi-select-selection"
46
41
  >
47
- text1, text2
42
+ <span>
43
+ text1
44
+ ,
45
+  
46
+ </span>
47
+ <span>
48
+ text2
49
+  
50
+ </span>
48
51
  </span>
49
52
  </div>
50
53
  `;
@@ -17,7 +17,6 @@ import type { TransitionStatus } from 'react-transition-group'
17
17
  import { PolymorphicRefForwardingComponent } from '../../helpers'
18
18
 
19
19
  import { CSidebarNavContext } from '../sidebar/CSidebarNavContext'
20
-
21
20
  export interface CNavGroupProps extends HTMLAttributes<HTMLDivElement | HTMLLIElement> {
22
21
  /**
23
22
  * Component used for the root node. Either a string to use a HTML element or a component.
@@ -36,7 +35,7 @@ export interface CNavGroupProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
36
35
  /**
37
36
  * Set group toggler label.
38
37
  */
39
- toggler?: string | ReactNode
38
+ toggler?: string | ReactNode | (({ visible }: { visible: boolean }) => ReactNode)
40
39
  /**
41
40
  * Show nav group items.
42
41
  */
@@ -69,8 +68,12 @@ export const CNavGroup: PolymorphicRefForwardingComponent<'li', CNavGroupProps>
69
68
  )
70
69
 
71
70
  useEffect(() => {
72
- setVisible(Boolean(idx && visibleGroup && isInVisibleGroup(visibleGroup, idx)))
73
- }, [visibleGroup])
71
+ visible !== undefined && setVisible(visible)
72
+ }, [visible])
73
+
74
+ useEffect(() => {
75
+ visibleGroup && setVisible(Boolean(idx && visibleGroup && isInVisibleGroup(visibleGroup, idx)))
76
+ }, [idx, visibleGroup])
74
77
 
75
78
  const handleTogglerOnCLick = (event: React.MouseEvent<HTMLElement>) => {
76
79
  event.preventDefault()
@@ -116,6 +119,7 @@ export const CNavGroup: PolymorphicRefForwardingComponent<'li', CNavGroupProps>
116
119
  }
117
120
 
118
121
  const NavGroupItemsComponent = Component === 'li' ? 'ul' : 'div'
122
+ const togglerContent = typeof toggler === 'function' ? toggler({ visible: _visible }) : toggler
119
123
 
120
124
  return (
121
125
  <Component
@@ -129,10 +133,11 @@ export const CNavGroup: PolymorphicRefForwardingComponent<'li', CNavGroupProps>
129
133
  href="#"
130
134
  onClick={(event) => handleTogglerOnCLick(event)}
131
135
  >
132
- {toggler}
136
+ {togglerContent}
133
137
  </a>
134
138
  )}
135
139
  <Transition
140
+ appear
136
141
  in={_visible}
137
142
  nodeRef={navItemsRef}
138
143
  onEntering={onEntering}
@@ -167,7 +172,7 @@ CNavGroup.propTypes = {
167
172
  className: PropTypes.string,
168
173
  compact: PropTypes.bool,
169
174
  idx: PropTypes.string,
170
- toggler: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
175
+ toggler: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]),
171
176
  visible: PropTypes.bool,
172
177
  }
173
178
 
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react'
2
- import { render } from '@testing-library/react'
2
+ import { fireEvent, render, screen } from '@testing-library/react'
3
3
  import '@testing-library/jest-dom'
4
4
  import { CNavGroup } from '../index'
5
+ import { CSidebarNavContext } from '../../sidebar/CSidebarNavContext'
5
6
 
6
7
  test('loads and displays CNavGroup component', async () => {
7
8
  const { container } = render(<CNavGroup toggler="anchorText" />)
@@ -23,3 +24,30 @@ test('CNavGroup customize', async () => {
23
24
  expect(true).toBe(false)
24
25
  }
25
26
  })
27
+
28
+ test('CNavGroup stays expanded when visible prop is set', async () => {
29
+ render(
30
+ <CSidebarNavContext.Provider value={{ visibleGroup: '', setVisibleGroup: jest.fn() }}>
31
+ <CNavGroup idx="1" toggler="anchorText" visible={true} />
32
+ </CSidebarNavContext.Provider>
33
+ )
34
+
35
+ expect(screen.getByRole('listitem')).toHaveClass('show')
36
+ })
37
+
38
+ test('CNavGroup toggler render function receives visible state', async () => {
39
+ render(
40
+ <CSidebarNavContext.Provider value={{ visibleGroup: '1', setVisibleGroup: jest.fn() }}>
41
+ <CNavGroup
42
+ idx="1"
43
+ toggler={({ visible }) => <span>{visible ? 'expanded' : 'collapsed'}</span>}
44
+ />
45
+ </CSidebarNavContext.Provider>
46
+ )
47
+
48
+ expect(screen.getByText('expanded')).toBeInTheDocument()
49
+
50
+ fireEvent.click(screen.getByRole('link'))
51
+
52
+ expect(screen.getByText('collapsed')).toBeInTheDocument()
53
+ })
@@ -3,7 +3,7 @@
3
3
  exports[`CNavGroup customize 1`] = `
4
4
  <div>
5
5
  <li
6
- class="nav-group bazinga"
6
+ class="nav-group show bazinga"
7
7
  >
8
8
  <a
9
9
  class="nav-link nav-group-toggle"
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react'
2
2
  import { render, fireEvent, act } from '@testing-library/react'
3
3
  import '@testing-library/jest-dom'
4
+
4
5
  import { COffcanvas } from '../index'
5
6
 
6
7
  test('loads and displays COffcanvas component', async () => {
@@ -1,9 +1,9 @@
1
1
  import React, {
2
- ChangeEvent,
3
2
  Children,
4
3
  ClipboardEvent,
5
4
  cloneElement,
6
5
  FocusEvent,
6
+ InputEvent,
7
7
  forwardRef,
8
8
  HTMLAttributes,
9
9
  isValidElement,
@@ -29,8 +29,7 @@ type ComponentWithDisplayName = {
29
29
  }
30
30
 
31
31
  export interface COneTimePasswordProps
32
- extends CFormControlWrapperProps,
33
- Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
32
+ extends CFormControlWrapperProps, Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
34
33
  /**
35
34
  * Function to generate aria-label for each input field. Receives current index (0-based) and total number of inputs.
36
35
  */
@@ -199,8 +198,8 @@ export const COneTimePassword = forwardRef<HTMLDivElement, COneTimePasswordProps
199
198
  )
200
199
 
201
200
  const handleInputChange = useCallback(
202
- (index: number, event: ChangeEvent<HTMLInputElement>) => {
203
- const inputValue = event.target.value
201
+ (index: number, event: InputEvent<HTMLInputElement>) => {
202
+ const inputValue = event.currentTarget.value
204
203
 
205
204
  if (inputValue.length === 1 && !isValidInput(inputValue, type)) {
206
205
  return
@@ -215,7 +214,7 @@ export const COneTimePassword = forwardRef<HTMLDivElement, COneTimePasswordProps
215
214
  if (inputValue.length === 1) {
216
215
  const nextInput = getNextActiveElement(
217
216
  inputRefs.current as HTMLInputElement[],
218
- event.target,
217
+ event.currentTarget,
219
218
  true,
220
219
  false
221
220
  )
@@ -402,19 +401,19 @@ export const COneTimePassword = forwardRef<HTMLDivElement, COneTimePasswordProps
402
401
 
403
402
  inputMode: type === 'number' ? 'numeric' : 'text',
404
403
  pattern: type === 'number' ? '[0-9]*' : '.*',
405
- onInput: (event: React.ChangeEvent<HTMLInputElement>) => {
404
+ onInput: (event: InputEvent<HTMLInputElement>) => {
406
405
  handleInputChange(currentInputIndex, event)
407
- inputChild.props.onChange?.(event)
406
+ inputChild.props.onInput?.(event)
408
407
  },
409
- onFocus: (event: React.FocusEvent<HTMLInputElement>) => {
408
+ onFocus: (event: FocusEvent<HTMLInputElement>) => {
410
409
  handleInputFocus(event)
411
410
  inputChild.props.onFocus?.(event)
412
411
  },
413
- onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {
412
+ onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => {
414
413
  handleKeyDown(event)
415
414
  inputChild.props.onKeyDown?.(event)
416
415
  },
417
- onPaste: (event: React.ClipboardEvent<HTMLInputElement>) => {
416
+ onPaste: (event: ClipboardEvent<HTMLInputElement>) => {
418
417
  handlePaste(index, event)
419
418
  inputChild.props.onPaste?.(event)
420
419
  },
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react'
2
2
  import { render } from '@testing-library/react'
3
- import '@testing-library/jest-dom/extend-expect'
3
+ import '@testing-library/jest-dom'
4
4
  import { CPicker } from '../CPicker'
5
5
 
6
6
  test('loads and displays CPicker component', async () => {
@@ -3,21 +3,7 @@
3
3
  exports[`loads and displays CPicker component 1`] = `
4
4
  <div>
5
5
  <div
6
- class="dropdown picker"
7
- >
8
- <button
9
- aria-expanded="false"
10
- class="btn btn-primary dropdown-toggle"
11
- tabindex="0"
12
- type="button"
13
- />
14
- <div
15
- aria-hidden="true"
16
- class="dropdown-menu"
17
- role="menu"
18
- >
19
- Test
20
- </div>
21
- </div>
6
+ class=""
7
+ />
22
8
  </div>
23
9
  `;
@@ -197,13 +197,24 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
197
197
  ...(typeof popperConfig === 'function' ? popperConfig(defaultPopperConfig) : popperConfig),
198
198
  }
199
199
 
200
+ const handleShow = () => {
201
+ setMounted(true)
202
+ onShow?.()
203
+ }
204
+
205
+ const handleHide = () => {
206
+ setTimeout(() => {
207
+ setVisible(false)
208
+ onHide?.()
209
+ }, _delay.hide)
210
+ }
211
+
200
212
  useEffect(() => {
201
- if (visible) {
213
+ if (visible === true) {
202
214
  handleShow()
203
- return
215
+ } else if (visible === false) {
216
+ handleHide()
204
217
  }
205
-
206
- handleHide()
207
218
  }, [visible])
208
219
 
209
220
  useEffect(() => {
@@ -229,22 +240,6 @@ export const CPopover = forwardRef<HTMLDivElement, CPopoverProps>(
229
240
  }
230
241
  }, [_visible])
231
242
 
232
- const handleShow = () => {
233
- setMounted(true)
234
- if (onShow) {
235
- onShow()
236
- }
237
- }
238
-
239
- const handleHide = () => {
240
- setTimeout(() => {
241
- setVisible(false)
242
- if (onHide) {
243
- onHide()
244
- }
245
- }, _delay.hide)
246
- }
247
-
248
243
  return (
249
244
  <>
250
245
  {React.cloneElement(children as React.ReactElement<any>, {
@@ -57,16 +57,16 @@ test('CPopover customize', async () => {
57
57
  test('CPopover onShow and onHide', async () => {
58
58
  jest.useFakeTimers()
59
59
 
60
+ const onShow = jest.fn()
61
+ const onHide = jest.fn()
62
+
60
63
  render(
61
- <CPopover content="content" title="title" trigger="click" placement="right" visible={true}>
64
+ <CPopover content="content" title="title" trigger="click" placement="right" onShow={onShow} onHide={onHide}>
62
65
  <CButton color="primary">Test</CButton>
63
66
  </CPopover>,
64
67
  { container: container! }
65
68
  )
66
69
 
67
- const onShow = jest.fn()
68
- const onHide = jest.fn()
69
-
70
70
  const btn = screen.getByRole('button', { name: /test/i })
71
71
 
72
72
  expect(onShow).toHaveBeenCalledTimes(0)
@@ -74,6 +74,9 @@ test('CPopover onShow and onHide', async () => {
74
74
 
75
75
  act(() => {
76
76
  fireEvent.click(btn)
77
+ })
78
+
79
+ act(() => {
77
80
  jest.runAllTimers()
78
81
  })
79
82
 
@@ -82,6 +85,9 @@ test('CPopover onShow and onHide', async () => {
82
85
 
83
86
  act(() => {
84
87
  fireEvent.click(btn)
88
+ })
89
+
90
+ act(() => {
85
91
  jest.runAllTimers()
86
92
  })
87
93
 
@@ -0,0 +1,195 @@
1
+ import React, {
2
+ ButtonHTMLAttributes,
3
+ MouseEvent,
4
+ ReactNode,
5
+ forwardRef,
6
+ useCallback,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from 'react'
12
+ import PropTypes from 'prop-types'
13
+ import classNames from 'classnames'
14
+
15
+ import { useForkedRef } from '../../hooks'
16
+ import {
17
+ formatShortcutTokens,
18
+ getPreferredShortcut,
19
+ getPressedKeys,
20
+ matchesShortcut,
21
+ parseShortcut,
22
+ shouldIgnoreShortcut,
23
+ } from './utils'
24
+
25
+ export interface CSearchButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
26
+ /**
27
+ * Content to customize the full button body.
28
+ */
29
+ children?: ReactNode
30
+ /**
31
+ * A string of all className you want applied to the base component.
32
+ */
33
+ className?: string
34
+ /**
35
+ * Custom icon displayed before the placeholder text.
36
+ */
37
+ icon?: ReactNode
38
+ /**
39
+ * Placeholder content rendered inside `.search-button-placeholder`.
40
+ */
41
+ placeholder?: ReactNode
42
+ /**
43
+ * Callback fired when the component is activated by click or keyboard shortcut.
44
+ */
45
+ onTrigger?: () => void
46
+ /**
47
+ * Prevent the browser's default behavior when the configured shortcut matches.
48
+ */
49
+ preventDefault?: boolean
50
+ /**
51
+ * Comma-separated shortcut list. The component matches all configured shortcuts and renders the platform-preferred one.
52
+ */
53
+ shortcut?: string
54
+ }
55
+
56
+ export const CSearchButton = forwardRef<HTMLButtonElement, CSearchButtonProps>(
57
+ (
58
+ {
59
+ children,
60
+ className,
61
+ disabled,
62
+ icon,
63
+ onClick,
64
+ onTrigger,
65
+ placeholder = 'Search',
66
+ preventDefault = true,
67
+ shortcut = 'meta+/,ctrl+/',
68
+ type = 'button',
69
+ ...rest
70
+ },
71
+ ref
72
+ ) => {
73
+ const buttonRef = useRef<HTMLButtonElement>(null)
74
+ const forkedRef = useForkedRef(ref, buttonRef)
75
+ const [activeKeys, setActiveKeys] = useState<string[]>([])
76
+ const shortcuts = useMemo(() => parseShortcut(shortcut), [shortcut])
77
+ const preferredShortcut = useMemo(() => getPreferredShortcut(shortcuts), [shortcuts])
78
+ const shortcutTokens = useMemo(
79
+ () => formatShortcutTokens(preferredShortcut?.shortcut || ''),
80
+ [preferredShortcut]
81
+ )
82
+
83
+ const handleShortcut = useCallback(
84
+ (event: KeyboardEvent) => {
85
+ if (disabled || event.defaultPrevented || event.repeat || shouldIgnoreShortcut(event)) {
86
+ return
87
+ }
88
+
89
+ const matchedShortcut = shortcuts.find((shortcut) => matchesShortcut(shortcut, event))
90
+
91
+ if (!matchedShortcut) {
92
+ return
93
+ }
94
+
95
+ if (preventDefault) {
96
+ event.preventDefault()
97
+ }
98
+
99
+ onTrigger?.()
100
+ },
101
+ [disabled, onTrigger, preventDefault, shortcuts]
102
+ )
103
+
104
+ const handleDocumentKeydown = useCallback(
105
+ (event: KeyboardEvent) => {
106
+ setActiveKeys(Array.from(getPressedKeys(event)))
107
+ handleShortcut(event)
108
+ },
109
+ [handleShortcut]
110
+ )
111
+
112
+ const handleDocumentKeyup = useCallback((event: KeyboardEvent) => {
113
+ setActiveKeys(Array.from(getPressedKeys(event)))
114
+ }, [])
115
+
116
+ const handleWindowBlur = useCallback(() => {
117
+ setActiveKeys([])
118
+ }, [])
119
+
120
+ useEffect(() => {
121
+ if (typeof document === 'undefined' || typeof window === 'undefined') {
122
+ return
123
+ }
124
+
125
+ document.addEventListener('keydown', handleDocumentKeydown)
126
+ document.addEventListener('keyup', handleDocumentKeyup)
127
+ window.addEventListener('blur', handleWindowBlur)
128
+
129
+ return () => {
130
+ document.removeEventListener('keydown', handleDocumentKeydown)
131
+ document.removeEventListener('keyup', handleDocumentKeyup)
132
+ window.removeEventListener('blur', handleWindowBlur)
133
+ }
134
+ }, [handleDocumentKeydown, handleDocumentKeyup, handleWindowBlur])
135
+
136
+ const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
137
+ onClick?.(event)
138
+ onTrigger?.()
139
+ }
140
+
141
+ return (
142
+ <button
143
+ type={type}
144
+ className={classNames('search-button', className)}
145
+ disabled={disabled}
146
+ onClick={handleClick}
147
+ {...rest}
148
+ ref={forkedRef}
149
+ >
150
+ {children ?? (
151
+ <>
152
+ {icon ?? (
153
+ <svg
154
+ className="search-button-icon"
155
+ xmlns="http://www.w3.org/2000/svg"
156
+ viewBox="0 0 512 512"
157
+ aria-hidden="true"
158
+ >
159
+ <path
160
+ fill="currentColor"
161
+ d="m479.6 399.716-81.084-81.084-62.368-25.767A175 175 0 0 0 368 192c0-97.047-78.953-176-176-176S16 94.953 16 192s78.953 176 176 176a175.03 175.03 0 0 0 101.619-32.377l25.7 62.2 81.081 81.088a56 56 0 1 0 79.2-79.195M48 192c0-79.4 64.6-144 144-144s144 64.6 144 144-64.6 144-144 144S48 271.4 48 192m408.971 264.284a24.03 24.03 0 0 1-33.942 0l-76.572-76.572-23.894-57.835 57.837 23.894 76.573 76.572a24.03 24.03 0 0 1-.002 33.941"
162
+ />
163
+ </svg>
164
+ )}
165
+ <span className="search-button-placeholder">{placeholder}</span>
166
+ </>
167
+ )}
168
+ <span className="search-button-keys" aria-hidden="true">
169
+ {shortcutTokens.map((key) => (
170
+ <span
171
+ className={classNames('search-button-key', { active: activeKeys.includes(key) })}
172
+ data-coreui-search-button-key={key}
173
+ key={key}
174
+ >
175
+ {key}
176
+ </span>
177
+ ))}
178
+ </span>
179
+ </button>
180
+ )
181
+ }
182
+ )
183
+
184
+ CSearchButton.propTypes = {
185
+ children: PropTypes.node,
186
+ className: PropTypes.string,
187
+ disabled: PropTypes.bool,
188
+ icon: PropTypes.node,
189
+ onTrigger: PropTypes.func,
190
+ placeholder: PropTypes.node,
191
+ preventDefault: PropTypes.bool,
192
+ shortcut: PropTypes.string,
193
+ }
194
+
195
+ CSearchButton.displayName = 'CSearchButton'
@@ -0,0 +1,95 @@
1
+ import * as React from 'react'
2
+ import { fireEvent, render, screen } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+ import { CSearchButton } from '../index'
5
+
6
+ const originalPlatform = window.navigator.platform
7
+
8
+ beforeEach(() => {
9
+ Object.defineProperty(window.navigator, 'platform', {
10
+ configurable: true,
11
+ value: 'MacIntel',
12
+ })
13
+ })
14
+
15
+ afterEach(() => {
16
+ Object.defineProperty(window.navigator, 'platform', {
17
+ configurable: true,
18
+ value: originalPlatform,
19
+ })
20
+ })
21
+
22
+ test('loads and displays CSearchButton component', async () => {
23
+ const { container } = render(<CSearchButton />)
24
+ expect(container).toMatchSnapshot()
25
+ })
26
+
27
+ test('CSearchButton customize', async () => {
28
+ const { container } = render(
29
+ <CSearchButton className="bazinga" placeholder="Command palette" shortcut="meta+k,ctrl+k" />,
30
+ )
31
+
32
+ expect(container).toMatchSnapshot()
33
+ expect(container.firstChild).toHaveClass('search-button')
34
+ expect(container.firstChild).toHaveClass('bazinga')
35
+ expect(screen.getByText('Command palette')).toBeInTheDocument()
36
+ expect(screen.getByText('⌘')).toBeInTheDocument()
37
+ expect(screen.getByText('K')).toBeInTheDocument()
38
+ })
39
+
40
+ test('CSearchButton triggers click on matching shortcut', async () => {
41
+ const onClick = jest.fn()
42
+ const onTrigger = jest.fn()
43
+
44
+ render(<CSearchButton onClick={onClick} onTrigger={onTrigger} shortcut="meta+k,ctrl+k" />)
45
+
46
+ fireEvent.keyDown(document, { key: 'k', metaKey: true })
47
+
48
+ expect(onClick).toHaveBeenCalledTimes(0)
49
+ expect(onTrigger).toHaveBeenCalledTimes(1)
50
+ })
51
+
52
+ test('CSearchButton ignores plain typing in editable fields', async () => {
53
+ const onClick = jest.fn()
54
+
55
+ render(
56
+ <>
57
+ <input aria-label="Search input" />
58
+ <CSearchButton onClick={onClick} shortcut="k" />
59
+ </>,
60
+ )
61
+
62
+ fireEvent.keyDown(screen.getByLabelText('Search input'), { key: 'k' })
63
+
64
+ expect(onClick).not.toHaveBeenCalled()
65
+ })
66
+
67
+ test('CSearchButton highlights active shortcut keys', async () => {
68
+ render(<CSearchButton shortcut="meta+k,ctrl+k" />)
69
+
70
+ fireEvent.keyDown(document, { key: 'Meta', metaKey: true })
71
+
72
+ expect(screen.getByText('⌘')).toHaveClass('active')
73
+
74
+ fireEvent.keyDown(document, { key: 'k', metaKey: true })
75
+
76
+ expect(screen.getByText('⌘')).toHaveClass('active')
77
+ expect(screen.getByText('K')).toHaveClass('active')
78
+
79
+ fireEvent.keyUp(document, { key: 'k' })
80
+
81
+ expect(screen.getByText('⌘')).not.toHaveClass('active')
82
+ expect(screen.getByText('K')).not.toHaveClass('active')
83
+ })
84
+
85
+ test('CSearchButton triggers onTrigger on click', async () => {
86
+ const onClick = jest.fn()
87
+ const onTrigger = jest.fn()
88
+
89
+ render(<CSearchButton onClick={onClick} onTrigger={onTrigger} />)
90
+
91
+ fireEvent.click(screen.getByRole('button'))
92
+
93
+ expect(onClick).toHaveBeenCalledTimes(1)
94
+ expect(onTrigger).toHaveBeenCalledTimes(1)
95
+ })