@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.
- package/README.md +1 -1
- package/dist/cjs/components/calendar/CCalendar.js +4 -3
- package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
- package/dist/cjs/components/date-picker/CDatePicker.d.ts +1 -1
- package/dist/cjs/components/date-picker/CDatePicker.js.map +1 -1
- package/dist/cjs/components/date-range-picker/CDateRangePicker.js +2 -0
- package/dist/cjs/components/date-range-picker/CDateRangePicker.js.map +1 -1
- package/dist/cjs/components/dropdown/CDropdown.js +1 -1
- package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/nav/CNavGroup.d.ts +3 -1
- package/dist/cjs/components/nav/CNavGroup.js +9 -5
- package/dist/cjs/components/nav/CNavGroup.js.map +1 -1
- package/dist/cjs/components/one-time-password-input/COneTimePassword.js +3 -3
- package/dist/cjs/components/one-time-password-input/COneTimePassword.js.map +1 -1
- package/dist/cjs/components/popover/CPopover.js +14 -17
- package/dist/cjs/components/popover/CPopover.js.map +1 -1
- package/dist/cjs/components/search-button/CSearchButton.d.ts +32 -0
- package/dist/cjs/components/search-button/CSearchButton.js +77 -0
- package/dist/cjs/components/search-button/CSearchButton.js.map +1 -0
- package/dist/cjs/components/search-button/index.d.ts +2 -0
- package/dist/cjs/components/search-button/types.d.ts +10 -0
- package/dist/cjs/components/search-button/utils.d.ts +11 -0
- package/dist/cjs/components/search-button/utils.js +115 -0
- package/dist/cjs/components/search-button/utils.js.map +1 -0
- package/dist/cjs/components/sidebar/CSidebarNav.d.ts +12 -0
- package/dist/cjs/components/sidebar/CSidebarNav.js +7 -2
- package/dist/cjs/components/sidebar/CSidebarNav.js.map +1 -1
- package/dist/cjs/components/smart-table/CSmartTable.js +2 -2
- package/dist/cjs/components/smart-table/CSmartTable.js.map +1 -1
- package/dist/cjs/components/smart-table/CSmartTableInterface.d.ts +2 -2
- package/dist/cjs/components/time-picker/CTimePicker.d.ts +1 -1
- package/dist/cjs/components/time-picker/CTimePicker.js +1 -1
- package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
- package/dist/cjs/components/tooltip/CTooltip.js +14 -17
- package/dist/cjs/components/tooltip/CTooltip.js.map +1 -1
- package/dist/cjs/components/virtual-scroller/CVirtualScroller.js +11 -8
- package/dist/cjs/components/virtual-scroller/CVirtualScroller.js.map +1 -1
- package/dist/cjs/hooks/useDebouncedCallback.d.ts +1 -1
- package/dist/cjs/hooks/useDebouncedCallback.js +1 -1
- package/dist/cjs/hooks/useDebouncedCallback.js.map +1 -1
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js.map +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js.map +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js.map +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js +1 -1
- package/dist/cjs/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js.map +1 -1
- package/dist/cjs/node_modules/ua-parser-js/dist/ua-parser.min.js +2 -2
- package/dist/cjs/node_modules/ua-parser-js/dist/ua-parser.min.js.map +1 -1
- package/dist/esm/components/calendar/CCalendar.js +4 -3
- package/dist/esm/components/calendar/CCalendar.js.map +1 -1
- package/dist/esm/components/date-picker/CDatePicker.d.ts +1 -1
- package/dist/esm/components/date-picker/CDatePicker.js.map +1 -1
- package/dist/esm/components/date-range-picker/CDateRangePicker.js +2 -0
- package/dist/esm/components/date-range-picker/CDateRangePicker.js.map +1 -1
- package/dist/esm/components/dropdown/CDropdown.js +1 -1
- package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/nav/CNavGroup.d.ts +3 -1
- package/dist/esm/components/nav/CNavGroup.js +9 -5
- package/dist/esm/components/nav/CNavGroup.js.map +1 -1
- package/dist/esm/components/one-time-password-input/COneTimePassword.js +3 -3
- package/dist/esm/components/one-time-password-input/COneTimePassword.js.map +1 -1
- package/dist/esm/components/popover/CPopover.js +14 -17
- package/dist/esm/components/popover/CPopover.js.map +1 -1
- package/dist/esm/components/search-button/CSearchButton.d.ts +32 -0
- package/dist/esm/components/search-button/CSearchButton.js +75 -0
- package/dist/esm/components/search-button/CSearchButton.js.map +1 -0
- package/dist/esm/components/search-button/index.d.ts +2 -0
- package/dist/esm/components/search-button/types.d.ts +10 -0
- package/dist/esm/components/search-button/utils.d.ts +11 -0
- package/dist/esm/components/search-button/utils.js +104 -0
- package/dist/esm/components/search-button/utils.js.map +1 -0
- package/dist/esm/components/sidebar/CSidebarNav.d.ts +12 -0
- package/dist/esm/components/sidebar/CSidebarNav.js +7 -2
- package/dist/esm/components/sidebar/CSidebarNav.js.map +1 -1
- package/dist/esm/components/smart-table/CSmartTable.js +2 -2
- package/dist/esm/components/smart-table/CSmartTable.js.map +1 -1
- package/dist/esm/components/smart-table/CSmartTableInterface.d.ts +2 -2
- package/dist/esm/components/time-picker/CTimePicker.d.ts +1 -1
- package/dist/esm/components/time-picker/CTimePicker.js +1 -1
- package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
- package/dist/esm/components/tooltip/CTooltip.js +14 -17
- package/dist/esm/components/tooltip/CTooltip.js.map +1 -1
- package/dist/esm/components/virtual-scroller/CVirtualScroller.js +11 -8
- package/dist/esm/components/virtual-scroller/CVirtualScroller.js.map +1 -1
- package/dist/esm/hooks/useDebouncedCallback.d.ts +1 -1
- package/dist/esm/hooks/useDebouncedCallback.js +1 -1
- package/dist/esm/hooks/useDebouncedCallback.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js.map +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js.map +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js.map +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js +1 -1
- package/dist/esm/node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js.map +1 -1
- package/dist/esm/node_modules/ua-parser-js/dist/ua-parser.min.js +2 -2
- package/dist/esm/node_modules/ua-parser-js/dist/ua-parser.min.js.map +1 -1
- package/package.json +7 -5
- package/src/components/autocomplete/__tests__/CAutocomplete.spec.tsx +18 -16
- package/src/components/autocomplete/__tests__/CAutocompleteOptions.spec.tsx +37 -28
- package/src/components/autocomplete/__tests__/__snapshots__/CAutocomplete.spec.tsx.snap +129 -0
- package/src/components/autocomplete/__tests__/__snapshots__/CAutocompleteOptions.spec.tsx.snap +36 -0
- package/src/components/calendar/CCalendar.tsx +5 -4
- package/src/components/calendar/__tests__/CCalendar.spec.tsx +40 -45
- package/src/components/calendar/__tests__/__snapshots__/CCalendar.spec.tsx.snap +99 -75
- package/src/components/calendar/__tests__/utils.spec.tsx +8 -18
- package/src/components/date-picker/CDatePicker.tsx +12 -13
- package/src/components/date-picker/__tests__/CDatePicker.spec.tsx +1 -1
- package/src/components/date-picker/__tests__/__snapshots__/CDatePicker.spec.tsx.snap +12 -640
- package/src/components/date-range-picker/CDateRangePicker.tsx +2 -0
- package/src/components/date-range-picker/__tests__/CDateRangePicker.spec.tsx +1 -1
- package/src/components/date-range-picker/__tests__/__snapshots__/CDateRangePicker.spec.tsx.snap +21 -1259
- package/src/components/dropdown/CDropdown.tsx +1 -1
- package/src/components/dropdown/__tests__/CDropdown.spec.tsx +1 -1
- package/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap +2 -2
- package/src/components/dropdown/__tests__/__snapshots__/CDropdownMenu.spec.tsx.snap +1 -1
- package/src/components/form/__tests__/CFormInput.spec.tsx +17 -20
- package/src/components/form/__tests__/__snapshots__/CFormInput.spec.tsx.snap +12 -3
- package/src/components/index.ts +1 -0
- package/src/components/loading-button/__tests__/CLoadingButton.spec.tsx +1 -1
- package/src/components/loading-button/__tests__/__snapshots__/CLoadingButton.spec.tsx.snap +4 -4
- package/src/components/multi-select/__tests__/CMultiSelect.spec.tsx +15 -61
- package/src/components/multi-select/__tests__/CMultiSelectNativeSelect.spec.tsx +4 -5
- package/src/components/multi-select/__tests__/CMultiSelectOptions.spec.tsx +4 -4
- package/src/components/multi-select/__tests__/CMultiSelectSelection.spec.tsx +7 -24
- package/src/components/multi-select/__tests__/__snapshots__/CMultiSelect.spec.tsx.snap +50 -20
- package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectNativeSelect.spec.tsx.snap +0 -2
- package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectOptions.spec.tsx.snap +6 -0
- package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectSelection.spec.tsx.snap +11 -8
- package/src/components/nav/CNavGroup.tsx +11 -6
- package/src/components/nav/__tests__/CNavGroup.spec.tsx +29 -1
- package/src/components/nav/__tests__/__snapshots__/CNavGroup.spec.tsx.snap +1 -1
- package/src/components/offcanvas/__tests__/COffcanvas.spec.tsx +1 -0
- package/src/components/one-time-password-input/COneTimePassword.tsx +10 -11
- package/src/components/picker/__tests__/CPicker.spec.tsx +1 -1
- package/src/components/picker/__tests__/__snapshots__/CPicker.spec.tsx.snap +2 -16
- package/src/components/popover/CPopover.tsx +15 -20
- package/src/components/popover/__tests__/CPopover.spec.tsx +10 -4
- package/src/components/search-button/CSearchButton.tsx +195 -0
- package/src/components/search-button/__tests__/CSearchButton.spec.tsx +95 -0
- package/src/components/search-button/__tests__/__snapshots__/CSearchButton.spec.tsx.snap +87 -0
- package/src/components/search-button/index.ts +3 -0
- package/src/components/search-button/types.ts +10 -0
- package/src/components/search-button/utils.ts +140 -0
- package/src/components/sidebar/CSidebarNav.tsx +27 -2
- package/src/components/smart-table/CSmartTable.tsx +5 -4
- package/src/components/smart-table/CSmartTableInterface.tsx +2 -2
- package/src/components/smart-table/__tests__/CSmartTable.spec.tsx +3 -2
- package/src/components/smart-table/__tests__/__snapshots__/CSmartTable.spec.tsx.snap +7 -15
- package/src/components/stepper/__tests__/CStepper.spec.tsx +24 -21
- package/src/components/stepper/__tests__/__snapshots__/CStepper.spec.tsx.snap +295 -0
- package/src/components/tabs/__tests__/CTabContent.spec.tsx +1 -0
- package/src/components/tabs/__tests__/CTabPane.spec.tsx +1 -0
- package/src/components/time-picker/CTimePicker.tsx +6 -7
- package/src/components/time-picker/__tests__/CTimePicker.spec.tsx +10 -1
- package/src/components/time-picker/__tests__/__snapshots__/CTimePicker.spec.tsx.snap +599 -315
- package/src/components/tooltip/CTooltip.tsx +15 -20
- package/src/components/tooltip/__tests__/CTooltip.spec.tsx +6 -0
- package/src/components/virtual-scroller/CVirtualScroller.tsx +13 -14
- package/src/hooks/useDebouncedCallback.ts +7 -4
- package/dist/cjs/components/accordion/CAccordionCollapse.d.ts +0 -3
- package/dist/esm/components/accordion/CAccordionCollapse.d.ts +0 -3
- package/src/components/accordion/CAccordionCollapse.tsx +0 -20
- package/src/components/accordion/__tests__/CAccordionCollapse.spec.tsx +0 -10
- package/src/components/accordion/__tests__/__snapshots__/CAccordionCollapse.spec.tsx.snap +0 -11
- package/src/components/button/__tests__/__snapshots__/CButtonClose.spec.tsx.snap +0 -26
- package/src/components/calendar/__tests__/CCalendar.accessibility.spec.tsx +0 -595
- package/src/components/calendar/__tests__/CCalendar.comprehensive.spec.tsx +0 -402
- package/src/components/calendar/__tests__/CCalendar.edge-cases.spec.tsx +0 -416
- package/src/components/calendar/__tests__/CCalendar.interactions.spec.tsx +0 -593
- package/src/components/header/CHeaderBrand.tsx~HEAD +0 -36
- /package/src/components/calendar/__tests__/{test-setup.ts → setup.ts} +0 -0
package/src/components/multi-select/__tests__/__snapshots__/CMultiSelectSelection.spec.tsx.snap
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
}, [
|
|
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
|
-
{
|
|
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
|
+
})
|
|
@@ -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:
|
|
203
|
-
const inputValue = event.
|
|
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.
|
|
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:
|
|
404
|
+
onInput: (event: InputEvent<HTMLInputElement>) => {
|
|
406
405
|
handleInputChange(currentInputIndex, event)
|
|
407
|
-
inputChild.props.
|
|
406
|
+
inputChild.props.onInput?.(event)
|
|
408
407
|
},
|
|
409
|
-
onFocus: (event:
|
|
408
|
+
onFocus: (event: FocusEvent<HTMLInputElement>) => {
|
|
410
409
|
handleInputFocus(event)
|
|
411
410
|
inputChild.props.onFocus?.(event)
|
|
412
411
|
},
|
|
413
|
-
onKeyDown: (event:
|
|
412
|
+
onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => {
|
|
414
413
|
handleKeyDown(event)
|
|
415
414
|
inputChild.props.onKeyDown?.(event)
|
|
416
415
|
},
|
|
417
|
-
onPaste: (event:
|
|
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
|
|
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="
|
|
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
|
-
|
|
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"
|
|
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
|
+
})
|