@coreui/react 5.9.2 → 5.11.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 (92) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/dist/cjs/components/chip/CChip.d.ts +76 -0
  4. package/dist/cjs/components/chip/CChip.js +178 -0
  5. package/dist/cjs/components/chip/CChip.js.map +1 -0
  6. package/dist/cjs/components/chip/index.d.ts +2 -0
  7. package/dist/cjs/components/dropdown/CDropdown.js +1 -1
  8. package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
  9. package/dist/cjs/components/form/CChipInput.d.ts +92 -0
  10. package/dist/cjs/components/form/CChipInput.js +253 -0
  11. package/dist/cjs/components/form/CChipInput.js.map +1 -0
  12. package/dist/cjs/components/form/index.d.ts +2 -1
  13. package/dist/cjs/components/index.d.ts +2 -0
  14. package/dist/cjs/components/nav/CNavGroup.d.ts +3 -1
  15. package/dist/cjs/components/nav/CNavGroup.js +9 -5
  16. package/dist/cjs/components/nav/CNavGroup.js.map +1 -1
  17. package/dist/cjs/components/popover/CPopover.js +14 -17
  18. package/dist/cjs/components/popover/CPopover.js.map +1 -1
  19. package/dist/cjs/components/search-button/CSearchButton.d.ts +32 -0
  20. package/dist/cjs/components/search-button/CSearchButton.js +77 -0
  21. package/dist/cjs/components/search-button/CSearchButton.js.map +1 -0
  22. package/dist/cjs/components/search-button/index.d.ts +2 -0
  23. package/dist/cjs/components/search-button/types.d.ts +10 -0
  24. package/dist/cjs/components/search-button/utils.d.ts +11 -0
  25. package/dist/cjs/components/search-button/utils.js +115 -0
  26. package/dist/cjs/components/search-button/utils.js.map +1 -0
  27. package/dist/cjs/components/sidebar/CSidebarNav.d.ts +12 -0
  28. package/dist/cjs/components/sidebar/CSidebarNav.js +7 -2
  29. package/dist/cjs/components/sidebar/CSidebarNav.js.map +1 -1
  30. package/dist/cjs/components/tooltip/CTooltip.js +14 -17
  31. package/dist/cjs/components/tooltip/CTooltip.js.map +1 -1
  32. package/dist/cjs/index.js +6 -0
  33. package/dist/cjs/index.js.map +1 -1
  34. package/dist/esm/components/chip/CChip.d.ts +76 -0
  35. package/dist/esm/components/chip/CChip.js +176 -0
  36. package/dist/esm/components/chip/CChip.js.map +1 -0
  37. package/dist/esm/components/chip/index.d.ts +2 -0
  38. package/dist/esm/components/dropdown/CDropdown.js +1 -1
  39. package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
  40. package/dist/esm/components/form/CChipInput.d.ts +92 -0
  41. package/dist/esm/components/form/CChipInput.js +251 -0
  42. package/dist/esm/components/form/CChipInput.js.map +1 -0
  43. package/dist/esm/components/form/index.d.ts +2 -1
  44. package/dist/esm/components/index.d.ts +2 -0
  45. package/dist/esm/components/nav/CNavGroup.d.ts +3 -1
  46. package/dist/esm/components/nav/CNavGroup.js +9 -5
  47. package/dist/esm/components/nav/CNavGroup.js.map +1 -1
  48. package/dist/esm/components/popover/CPopover.js +14 -17
  49. package/dist/esm/components/popover/CPopover.js.map +1 -1
  50. package/dist/esm/components/search-button/CSearchButton.d.ts +32 -0
  51. package/dist/esm/components/search-button/CSearchButton.js +75 -0
  52. package/dist/esm/components/search-button/CSearchButton.js.map +1 -0
  53. package/dist/esm/components/search-button/index.d.ts +2 -0
  54. package/dist/esm/components/search-button/types.d.ts +10 -0
  55. package/dist/esm/components/search-button/utils.d.ts +11 -0
  56. package/dist/esm/components/search-button/utils.js +104 -0
  57. package/dist/esm/components/search-button/utils.js.map +1 -0
  58. package/dist/esm/components/sidebar/CSidebarNav.d.ts +12 -0
  59. package/dist/esm/components/sidebar/CSidebarNav.js +7 -2
  60. package/dist/esm/components/sidebar/CSidebarNav.js.map +1 -1
  61. package/dist/esm/components/tooltip/CTooltip.js +14 -17
  62. package/dist/esm/components/tooltip/CTooltip.js.map +1 -1
  63. package/dist/esm/index.js +3 -0
  64. package/dist/esm/index.js.map +1 -1
  65. package/package.json +7 -7
  66. package/src/components/chip/CChip.tsx +372 -0
  67. package/src/components/chip/__tests__/CChip.spec.tsx +113 -0
  68. package/src/components/chip/__tests__/__snapshots__/CChip.spec.tsx.snap +65 -0
  69. package/src/components/chip/index.ts +3 -0
  70. package/src/components/dropdown/CDropdown.tsx +1 -1
  71. package/src/components/dropdown/__tests__/CDropdown.spec.tsx +1 -1
  72. package/src/components/dropdown/__tests__/__snapshots__/CDropdown.spec.tsx.snap +2 -2
  73. package/src/components/dropdown/__tests__/__snapshots__/CDropdownMenu.spec.tsx.snap +1 -1
  74. package/src/components/form/CChipInput.tsx +477 -0
  75. package/src/components/form/__tests__/CChipInput.spec.tsx +62 -0
  76. package/src/components/form/__tests__/__snapshots__/CChipInput.spec.tsx.snap +91 -0
  77. package/src/components/form/index.ts +2 -0
  78. package/src/components/index.ts +2 -0
  79. package/src/components/nav/CNavGroup.tsx +11 -6
  80. package/src/components/nav/__tests__/CNavGroup.spec.tsx +29 -1
  81. package/src/components/nav/__tests__/__snapshots__/CNavGroup.spec.tsx.snap +1 -1
  82. package/src/components/popover/CPopover.tsx +15 -20
  83. package/src/components/popover/__tests__/CPopover.spec.tsx +10 -4
  84. package/src/components/search-button/CSearchButton.tsx +195 -0
  85. package/src/components/search-button/__tests__/CSearchButton.spec.tsx +95 -0
  86. package/src/components/search-button/__tests__/__snapshots__/CSearchButton.spec.tsx.snap +87 -0
  87. package/src/components/search-button/index.ts +3 -0
  88. package/src/components/search-button/types.ts +10 -0
  89. package/src/components/search-button/utils.ts +140 -0
  90. package/src/components/sidebar/CSidebarNav.tsx +27 -2
  91. package/src/components/tooltip/CTooltip.tsx +15 -20
  92. package/src/components/tooltip/__tests__/CTooltip.spec.tsx +6 -0
@@ -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"
@@ -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
+ })
@@ -0,0 +1,87 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`CSearchButton customize 1`] = `
4
+ <div>
5
+ <button
6
+ class="search-button bazinga"
7
+ type="button"
8
+ >
9
+ <svg
10
+ aria-hidden="true"
11
+ class="search-button-icon"
12
+ viewBox="0 0 512 512"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ >
15
+ <path
16
+ 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"
17
+ fill="currentColor"
18
+ />
19
+ </svg>
20
+ <span
21
+ class="search-button-placeholder"
22
+ >
23
+ Command palette
24
+ </span>
25
+ <span
26
+ aria-hidden="true"
27
+ class="search-button-keys"
28
+ >
29
+ <span
30
+ class="search-button-key"
31
+ data-coreui-search-button-key="⌘"
32
+ >
33
+
34
+ </span>
35
+ <span
36
+ class="search-button-key"
37
+ data-coreui-search-button-key="K"
38
+ >
39
+ K
40
+ </span>
41
+ </span>
42
+ </button>
43
+ </div>
44
+ `;
45
+
46
+ exports[`loads and displays CSearchButton component 1`] = `
47
+ <div>
48
+ <button
49
+ class="search-button"
50
+ type="button"
51
+ >
52
+ <svg
53
+ aria-hidden="true"
54
+ class="search-button-icon"
55
+ viewBox="0 0 512 512"
56
+ xmlns="http://www.w3.org/2000/svg"
57
+ >
58
+ <path
59
+ 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"
60
+ fill="currentColor"
61
+ />
62
+ </svg>
63
+ <span
64
+ class="search-button-placeholder"
65
+ >
66
+ Search
67
+ </span>
68
+ <span
69
+ aria-hidden="true"
70
+ class="search-button-keys"
71
+ >
72
+ <span
73
+ class="search-button-key"
74
+ data-coreui-search-button-key="⌘"
75
+ >
76
+
77
+ </span>
78
+ <span
79
+ class="search-button-key"
80
+ data-coreui-search-button-key="/"
81
+ >
82
+ /
83
+ </span>
84
+ </span>
85
+ </button>
86
+ </div>
87
+ `;
@@ -0,0 +1,3 @@
1
+ import { CSearchButton } from './CSearchButton'
2
+
3
+ export { CSearchButton }
@@ -0,0 +1,10 @@
1
+ export type SearchButtonShortcut = {
2
+ key: string
3
+ modifiers: {
4
+ alt: boolean
5
+ ctrl: boolean
6
+ meta: boolean
7
+ shift: boolean
8
+ }
9
+ shortcut: string
10
+ }