@coreui/react-pro 5.0.0 → 5.1.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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/calendar/CCalendar.js +0 -1
  3. package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
  4. package/dist/cjs/components/dropdown/utils.js +1 -1
  5. package/dist/cjs/components/dropdown/utils.js.map +1 -1
  6. package/dist/cjs/components/index.d.ts +1 -0
  7. package/dist/cjs/components/multi-select/utils.js +0 -6
  8. package/dist/cjs/components/multi-select/utils.js.map +1 -1
  9. package/dist/cjs/components/popover/CPopover.js +41 -40
  10. package/dist/cjs/components/popover/CPopover.js.map +1 -1
  11. package/dist/cjs/components/rating/CRating.d.ts +70 -0
  12. package/dist/cjs/components/rating/CRating.js +133 -0
  13. package/dist/cjs/components/rating/CRating.js.map +1 -0
  14. package/dist/cjs/components/rating/index.d.ts +2 -0
  15. package/dist/cjs/components/tooltip/CTooltip.js +44 -40
  16. package/dist/cjs/components/tooltip/CTooltip.js.map +1 -1
  17. package/dist/cjs/hooks/usePopper.d.ts +1 -0
  18. package/dist/cjs/hooks/usePopper.js +11 -4
  19. package/dist/cjs/hooks/usePopper.js.map +1 -1
  20. package/dist/cjs/hooks/useStateWithCallback.d.ts +1 -1
  21. package/dist/cjs/index.js +2 -0
  22. package/dist/cjs/index.js.map +1 -1
  23. package/dist/cjs/node_modules/classnames/index.js +45 -27
  24. package/dist/cjs/node_modules/classnames/index.js.map +1 -1
  25. package/dist/cjs/node_modules/react-device-detect/dist/lib.js +0 -1
  26. package/dist/cjs/node_modules/react-device-detect/dist/lib.js.map +1 -1
  27. package/dist/cjs/node_modules/react-transition-group/esm/CSSTransition.js +1 -2
  28. package/dist/cjs/node_modules/react-transition-group/esm/CSSTransition.js.map +1 -1
  29. package/dist/cjs/node_modules/react-transition-group/esm/Transition.js +1 -2
  30. package/dist/cjs/node_modules/react-transition-group/esm/Transition.js.map +1 -1
  31. package/dist/esm/components/calendar/CCalendar.js +0 -1
  32. package/dist/esm/components/calendar/CCalendar.js.map +1 -1
  33. package/dist/esm/components/dropdown/utils.js +1 -1
  34. package/dist/esm/components/dropdown/utils.js.map +1 -1
  35. package/dist/esm/components/index.d.ts +1 -0
  36. package/dist/esm/components/multi-select/utils.js +0 -6
  37. package/dist/esm/components/multi-select/utils.js.map +1 -1
  38. package/dist/esm/components/popover/CPopover.js +41 -40
  39. package/dist/esm/components/popover/CPopover.js.map +1 -1
  40. package/dist/esm/components/rating/CRating.d.ts +70 -0
  41. package/dist/esm/components/rating/CRating.js +131 -0
  42. package/dist/esm/components/rating/CRating.js.map +1 -0
  43. package/dist/esm/components/rating/index.d.ts +2 -0
  44. package/dist/esm/components/tooltip/CTooltip.js +44 -40
  45. package/dist/esm/components/tooltip/CTooltip.js.map +1 -1
  46. package/dist/esm/hooks/usePopper.d.ts +1 -0
  47. package/dist/esm/hooks/usePopper.js +11 -4
  48. package/dist/esm/hooks/usePopper.js.map +1 -1
  49. package/dist/esm/hooks/useStateWithCallback.d.ts +1 -1
  50. package/dist/esm/index.js +1 -0
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/node_modules/classnames/index.js +45 -27
  53. package/dist/esm/node_modules/classnames/index.js.map +1 -1
  54. package/dist/esm/node_modules/react-device-detect/dist/lib.js +0 -1
  55. package/dist/esm/node_modules/react-device-detect/dist/lib.js.map +1 -1
  56. package/dist/esm/node_modules/react-transition-group/esm/CSSTransition.js +1 -2
  57. package/dist/esm/node_modules/react-transition-group/esm/CSSTransition.js.map +1 -1
  58. package/dist/esm/node_modules/react-transition-group/esm/Transition.js +1 -2
  59. package/dist/esm/node_modules/react-transition-group/esm/Transition.js.map +1 -1
  60. package/package.json +10 -10
  61. package/src/components/index.ts +1 -0
  62. package/src/components/popover/CPopover.tsx +51 -66
  63. package/src/components/rating/CRating.tsx +313 -0
  64. package/src/components/rating/index.ts +3 -0
  65. package/src/components/tooltip/CTooltip.tsx +55 -66
  66. package/src/hooks/usePopper.ts +15 -5
@@ -0,0 +1,313 @@
1
+ import React, {
2
+ forwardRef,
3
+ Fragment,
4
+ HTMLAttributes,
5
+ ReactNode,
6
+ useEffect,
7
+ useId,
8
+ useState,
9
+ } from 'react'
10
+ import PropTypes from 'prop-types'
11
+ import classNames from 'classnames'
12
+ import { CTooltip } from '../tooltip'
13
+
14
+ type Icon = {
15
+ [key: number]: JSX.Element
16
+ }
17
+
18
+ export interface CRatingProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
19
+ /**
20
+ * The default icon to display when the item is selected.
21
+ */
22
+ activeIcon?: string | JSX.Element | Icon
23
+ /**
24
+ * Enables the clearing upon clicking the selected item again.
25
+ */
26
+ allowClear?: boolean
27
+ /**
28
+ * A string of all className you want applied to the component.
29
+ */
30
+ className?: string
31
+ /**
32
+ * Toggle the disabled state for the component.
33
+ */
34
+ disabled?: boolean
35
+ /**
36
+ * If enabled, only the currently selected icon will be visibly highlighted.
37
+ */
38
+ highlightOnlySelected?: boolean
39
+ /**
40
+ * The default icon to display when the item is not selected.
41
+ */
42
+ icon?: string | JSX.Element | Icon
43
+ /**
44
+ * Specifies the total number of stars to be displayed in the star rating component. This property determines the scale of the rating, such as out of 5 stars, 10 stars, etc.
45
+ */
46
+ itemCount?: number
47
+ /**
48
+ * The name attribute of the radio input elements.
49
+ */
50
+ name?: string
51
+ /**
52
+ * Execute a function when a user changes the selected element.
53
+ */
54
+ onChange?: (value: number | null) => void
55
+ /**
56
+ * Execute a function when a user hover the element.
57
+ */
58
+ onHover?: (value: number | null) => void
59
+ /**
60
+ * Minimum increment value change allowed.
61
+ */
62
+ precision?: number
63
+ /**
64
+ * Toggle the readonly state for the component.
65
+ */
66
+ readOnly?: boolean
67
+ /**
68
+ * Size the component small, large, or custom if you define custom icons with custom height.
69
+ */
70
+ size?: 'sm' | 'lg' | 'custom'
71
+ /**
72
+ * Enable tooltips with default values or set specific labels for each icon.
73
+ */
74
+ tooltips?: boolean | string[]
75
+ /**
76
+ * The `value` attribute of component.
77
+ *
78
+ * @controllable onChange
79
+ * */
80
+ value?: number
81
+ }
82
+
83
+ const CConditionalTooltip = ({
84
+ children,
85
+ content,
86
+ tooltip,
87
+ }: {
88
+ children: ReactNode
89
+ content?: ReactNode
90
+ tooltip?: boolean
91
+ }) => {
92
+ return tooltip ? (
93
+ <CTooltip content={content}>
94
+ <span>{children}</span>
95
+ </CTooltip>
96
+ ) : (
97
+ children
98
+ )
99
+ }
100
+
101
+ export const CRating = forwardRef<HTMLDivElement, CRatingProps>(
102
+ (
103
+ {
104
+ activeIcon,
105
+ allowClear,
106
+ className,
107
+ disabled,
108
+ highlightOnlySelected,
109
+ icon,
110
+ itemCount = 5,
111
+ name,
112
+ onChange,
113
+ onHover,
114
+ precision = 1,
115
+ readOnly,
116
+ size,
117
+ tooltips,
118
+ value,
119
+ ...rest
120
+ },
121
+ ref,
122
+ ) => {
123
+ const [cleared, setCleared] = useState(false)
124
+ const [currentValue, setCurrentValue] = useState(value ?? null)
125
+ const [hoverValue, setHoverValue] = useState<number | null>(null)
126
+ const [tooltipValue, setTooltipValue] = useState<number | string | null>(null)
127
+ const _name = name || useId()
128
+
129
+ useEffect(() => {
130
+ value !== undefined && setCurrentValue(value)
131
+ }, [value])
132
+
133
+ const handleMouseEnter = (value: number) => {
134
+ if (disabled || readOnly) {
135
+ return
136
+ }
137
+
138
+ onHover && onHover(value)
139
+ setHoverValue(value)
140
+ value && setTooltipValue(value)
141
+ }
142
+
143
+ const handleMouseLeave = () => {
144
+ if (disabled || readOnly) {
145
+ return
146
+ }
147
+
148
+ onHover && onHover(null)
149
+ setHoverValue(null)
150
+ }
151
+
152
+ const handleOnChange = (value: number) => {
153
+ if (disabled || readOnly) {
154
+ return
155
+ }
156
+
157
+ if (cleared) {
158
+ setCleared(false)
159
+ return
160
+ }
161
+
162
+ setCurrentValue(value)
163
+ onChange && onChange(value)
164
+ }
165
+
166
+ const handleOnClick = (value: number) => {
167
+ if (disabled || readOnly) {
168
+ return
169
+ }
170
+
171
+ if (allowClear && value === currentValue) {
172
+ setCleared(true)
173
+ onChange && onChange(null)
174
+ setCurrentValue(null)
175
+ setHoverValue(null)
176
+ }
177
+ }
178
+
179
+ return (
180
+ <div
181
+ className={classNames(
182
+ 'rating',
183
+ {
184
+ [`rating-${size}`]: size,
185
+ disabled: disabled,
186
+ readonly: readOnly,
187
+ },
188
+ className,
189
+ )}
190
+ role="radiogroup"
191
+ {...rest}
192
+ ref={ref}
193
+ >
194
+ {Array.from({ length: itemCount }, (_, index) => {
195
+ const numberOfRadios = 1 / precision
196
+
197
+ return (
198
+ <CConditionalTooltip
199
+ key={index}
200
+ {...(tooltips && {
201
+ content: Array.isArray(tooltips)
202
+ ? tooltips[index]
203
+ : precision
204
+ ? tooltipValue
205
+ : index + 1,
206
+ tooltip: true,
207
+ })}
208
+ >
209
+ <div className="rating-item">
210
+ {Array.from({ length: numberOfRadios }, (_, _index) => {
211
+ const isNotLastItem = _index + 1 < numberOfRadios
212
+ const value =
213
+ numberOfRadios === 1 ? index + 1 : index + (_index + 1) * (1 * precision)
214
+ const id = useId()
215
+
216
+ const isItemChecked = () => value === currentValue
217
+
218
+ const isItemActive = () => {
219
+ if (
220
+ highlightOnlySelected
221
+ ? hoverValue === value
222
+ : hoverValue && hoverValue >= value
223
+ ) {
224
+ return true
225
+ }
226
+
227
+ if (
228
+ hoverValue === null &&
229
+ (highlightOnlySelected
230
+ ? isItemChecked()
231
+ : currentValue && currentValue >= value)
232
+ ) {
233
+ return true
234
+ }
235
+
236
+ return false
237
+ }
238
+
239
+ return (
240
+ <Fragment key={value}>
241
+ <label
242
+ className={classNames('rating-item-label', {
243
+ active: isItemActive(),
244
+ })}
245
+ htmlFor={id}
246
+ onClick={() => handleOnClick(value)}
247
+ onMouseEnter={() => handleMouseEnter(value)}
248
+ onMouseLeave={() => handleMouseLeave()}
249
+ {...(isNotLastItem && {
250
+ style: {
251
+ zIndex: 1 / precision - _index,
252
+ position: 'absolute',
253
+ width: `${precision * (_index + 1) * 100}%`,
254
+ overflow: 'hidden',
255
+ opacity: 0,
256
+ },
257
+ })}
258
+ >
259
+ {icon ? (
260
+ <div className="rating-item-custom-icon">
261
+ {(icon as Icon)[value] ?? icon}
262
+ </div>
263
+ ) : (
264
+ <div className="rating-item-icon"></div>
265
+ )}
266
+ {activeIcon && (
267
+ <div className="rating-item-custom-icon-active">
268
+ {(activeIcon as Icon)[value] ?? activeIcon}
269
+ </div>
270
+ )}
271
+ </label>
272
+ <input
273
+ className="rating-item-input"
274
+ checked={isItemChecked()}
275
+ disabled={disabled || readOnly}
276
+ id={id}
277
+ name={_name}
278
+ onBlur={() => handleMouseLeave()}
279
+ onChange={() => handleOnChange(value)}
280
+ onFocus={() => handleMouseEnter(value)}
281
+ type="radio"
282
+ value={value}
283
+ />
284
+ </Fragment>
285
+ )
286
+ })}
287
+ </div>
288
+ </CConditionalTooltip>
289
+ )
290
+ })}
291
+ </div>
292
+ )
293
+ },
294
+ )
295
+
296
+ CRating.propTypes = {
297
+ children: PropTypes.node,
298
+ activeIcon: PropTypes.any,
299
+ allowClear: PropTypes.bool,
300
+ disabled: PropTypes.bool,
301
+ highlightOnlySelected: PropTypes.bool,
302
+ icon: PropTypes.any,
303
+ itemCount: PropTypes.number,
304
+ name: PropTypes.string,
305
+ onChange: PropTypes.func,
306
+ onHover: PropTypes.func,
307
+ precision: PropTypes.number,
308
+ readOnly: PropTypes.bool,
309
+ size: PropTypes.oneOf(['sm', 'lg', 'custom']),
310
+ value: PropTypes.number,
311
+ }
312
+
313
+ CRating.displayName = 'CRating'
@@ -0,0 +1,3 @@
1
+ import { CRating } from './CRating'
2
+
3
+ export { CRating }
@@ -1,13 +1,12 @@
1
1
  import React, { forwardRef, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
2
  import classNames from 'classnames'
3
3
  import PropTypes from 'prop-types'
4
- import { Transition } from 'react-transition-group'
5
4
 
6
5
  import { CConditionalPortal } from '../conditional-portal'
7
6
  import { useForkedRef, usePopper } from '../../hooks'
8
7
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
8
  import type { Placements, Triggers } from '../../types'
10
- import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
9
+ import { executeAfterTransition, getRTLPlacement } from '../../utils'
11
10
 
12
11
  export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {
13
12
  /**
@@ -95,7 +94,8 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
95
94
  const forkedRef = useForkedRef(ref, tooltipRef)
96
95
  const uID = useRef(`tooltip${Math.floor(Math.random() * 1_000_000)}`)
97
96
 
98
- const { initPopper, destroyPopper } = usePopper()
97
+ const { initPopper, destroyPopper, updatePopper } = usePopper()
98
+ const [mounted, setMounted] = useState(false)
99
99
  const [_visible, setVisible] = useState(visible)
100
100
 
101
101
  const _delay = typeof delay === 'number' ? { show: delay, hide: delay } : delay
@@ -128,14 +128,43 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
128
128
  setVisible(visible)
129
129
  }, [visible])
130
130
 
131
- const toggleVisible = (visible: boolean) => {
132
- if (visible) {
133
- setTimeout(() => setVisible(true), _delay.show)
134
- return
131
+ useEffect(() => {
132
+ if (_visible) {
133
+ setMounted(true)
134
+
135
+ if (tooltipRef.current) {
136
+ tooltipRef.current.classList.remove('fade', 'show')
137
+ destroyPopper()
138
+ }
139
+
140
+ setTimeout(() => {
141
+ if (togglerRef.current && tooltipRef.current) {
142
+ if (animation) {
143
+ tooltipRef.current.classList.add('fade')
144
+ }
145
+
146
+ initPopper(togglerRef.current, tooltipRef.current, popperConfig)
147
+ tooltipRef.current.classList.add('show')
148
+ onShow && onShow()
149
+ }
150
+ }, _delay.show)
135
151
  }
136
152
 
137
- setTimeout(() => setVisible(false), _delay.hide)
138
- }
153
+ return () => {
154
+ if (tooltipRef.current) {
155
+ tooltipRef.current.classList.remove('show')
156
+ onHide && onHide()
157
+ executeAfterTransition(() => {
158
+ destroyPopper()
159
+ setMounted(false)
160
+ }, tooltipRef.current)
161
+ }
162
+ }
163
+ }, [_visible])
164
+
165
+ useEffect(() => {
166
+ updatePopper()
167
+ }, [content])
139
168
 
140
169
  return (
141
170
  <>
@@ -145,70 +174,30 @@ export const CTooltip = forwardRef<HTMLDivElement, CTooltipProps>(
145
174
  }),
146
175
  ref: togglerRef,
147
176
  ...((trigger === 'click' || trigger.includes('click')) && {
148
- onClick: () => toggleVisible(!_visible),
177
+ onClick: () => setVisible(!_visible),
149
178
  }),
150
179
  ...((trigger === 'focus' || trigger.includes('focus')) && {
151
- onFocus: () => toggleVisible(true),
152
- onBlur: () => toggleVisible(false),
180
+ onFocus: () => setVisible(true),
181
+ onBlur: () => setVisible(false),
153
182
  }),
154
183
  ...((trigger === 'hover' || trigger.includes('hover')) && {
155
- onMouseEnter: () => toggleVisible(true),
156
- onMouseLeave: () => toggleVisible(false),
184
+ onMouseEnter: () => setVisible(true),
185
+ onMouseLeave: () => setVisible(false),
157
186
  }),
158
187
  })}
159
188
  <CConditionalPortal container={container} portal={true}>
160
- <Transition
161
- in={_visible}
162
- mountOnEnter
163
- nodeRef={tooltipRef}
164
- onEnter={() => {
165
- if (togglerRef.current && tooltipRef.current) {
166
- initPopper(togglerRef.current, tooltipRef.current, popperConfig)
167
- }
168
-
169
- onShow
170
- }}
171
- onEntering={() => {
172
- if (togglerRef.current && tooltipRef.current) {
173
- tooltipRef.current.style.display = 'initial'
174
- }
175
- }}
176
- onExit={onHide}
177
- onExited={() => {
178
- destroyPopper()
179
- }}
180
- timeout={{
181
- enter: 0,
182
- exit: tooltipRef.current
183
- ? getTransitionDurationFromElement(tooltipRef.current) + 50
184
- : 200,
185
- }}
186
- unmountOnExit
187
- >
188
- {(state) => (
189
- <div
190
- className={classNames(
191
- 'tooltip',
192
- 'bs-tooltip-auto',
193
- {
194
- fade: animation,
195
- show: state === 'entered',
196
- },
197
- className,
198
- )}
199
- id={uID.current}
200
- ref={forkedRef}
201
- role="tooltip"
202
- style={{
203
- display: 'none',
204
- }}
205
- {...rest}
206
- >
207
- <div className="tooltip-arrow"></div>
208
- <div className="tooltip-inner">{content}</div>
209
- </div>
210
- )}
211
- </Transition>
189
+ {mounted && (
190
+ <div
191
+ className={classNames('tooltip', 'bs-tooltip-auto', className)}
192
+ id={uID.current}
193
+ ref={forkedRef}
194
+ role="tooltip"
195
+ {...rest}
196
+ >
197
+ <div className="tooltip-arrow"></div>
198
+ <div className="tooltip-inner">{content}</div>
199
+ </div>
200
+ )}
212
201
  </CConditionalPortal>
213
202
  </>
214
203
  )
@@ -2,12 +2,11 @@ import { useRef } from 'react'
2
2
  import { createPopper } from '@popperjs/core'
3
3
  import type { Instance, Options } from '@popperjs/core'
4
4
 
5
- import { executeAfterTransition } from '../utils'
6
-
7
5
  interface UsePopperOutput {
8
6
  popper: Instance | undefined
9
7
  initPopper: (reference: HTMLElement, popper: HTMLElement, options: Partial<Options>) => void
10
8
  destroyPopper: () => void
9
+ updatePopper: (options?: Partial<Options>) => void
11
10
  }
12
11
 
13
12
  export const usePopper = (): UsePopperOutput => {
@@ -23,17 +22,28 @@ export const usePopper = (): UsePopperOutput => {
23
22
  const popperInstance = _popper.current
24
23
 
25
24
  if (popperInstance && el.current) {
26
- executeAfterTransition(() => {
27
- popperInstance.destroy()
28
- }, el.current)
25
+ popperInstance.destroy()
29
26
  }
30
27
 
31
28
  _popper.current = undefined
32
29
  }
33
30
 
31
+ const updatePopper = (options?: Partial<Options>) => {
32
+ const popperInstance = _popper.current
33
+
34
+ if (popperInstance && options) {
35
+ popperInstance.setOptions(options)
36
+ }
37
+
38
+ if (popperInstance) {
39
+ popperInstance.update()
40
+ }
41
+ }
42
+
34
43
  return {
35
44
  popper: _popper.current,
36
45
  initPopper,
37
46
  destroyPopper,
47
+ updatePopper,
38
48
  }
39
49
  }