@coreui/react 4.8.0 → 4.9.0-alpha.1

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.
@@ -1,2 +1,3 @@
1
1
  import isInViewport from './isInViewport';
2
- export { isInViewport };
2
+ import isRTL from './isRTL';
3
+ export { isInViewport, isRTL };
@@ -0,0 +1,2 @@
1
+ declare const isRTL: (element?: HTMLElement | HTMLDivElement | null) => boolean;
2
+ export default isRTL;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreui/react",
3
- "version": "4.8.0",
3
+ "version": "4.9.0-alpha.1",
4
4
  "description": "UI Components Library for React.js",
5
5
  "keywords": [
6
6
  "react",
@@ -37,15 +37,15 @@
37
37
  "test:update": "jest --coverage --updateSnapshot"
38
38
  },
39
39
  "devDependencies": {
40
- "@popperjs/core": "^2.11.6",
41
- "@rollup/plugin-commonjs": "^24.0.1",
42
- "@rollup/plugin-node-resolve": "^15.0.1",
43
- "@rollup/plugin-typescript": "^11.0.0",
40
+ "@popperjs/core": "^2.11.7",
41
+ "@rollup/plugin-commonjs": "^24.1.0",
42
+ "@rollup/plugin-node-resolve": "^15.0.2",
43
+ "@rollup/plugin-typescript": "^11.1.0",
44
44
  "@testing-library/jest-dom": "^5.16.5",
45
45
  "@testing-library/react": "^14.0.0",
46
- "@types/react": "18.0.28",
47
- "@types/react-dom": "^18.0.11",
48
- "@types/react-transition-group": "^4.4.5",
46
+ "@types/react": "18.2.6",
47
+ "@types/react-dom": "^18.2.4",
48
+ "@types/react-transition-group": "^4.4.6",
49
49
  "classnames": "^2.3.2",
50
50
  "jest": "^29.5.0",
51
51
  "jest-environment-jsdom": "^29.5.0",
@@ -54,12 +54,13 @@
54
54
  "react-dom": "^18.2.0",
55
55
  "react-popper": "^2.3.0",
56
56
  "react-transition-group": "^4.4.5",
57
- "rollup": "^3.19.1",
57
+ "rollup": "^3.21.6",
58
58
  "tslib": "^2.5.0",
59
- "ts-jest": "^29.0.5",
59
+ "ts-jest": "^29.1.0",
60
60
  "typescript": "^4.9.5"
61
61
  },
62
62
  "peerDependencies": {
63
+ "@coreui/coreui": "4.3.0-alpha.0",
63
64
  "react": ">=17",
64
65
  "react-dom": ">=17"
65
66
  }
@@ -7,6 +7,7 @@ import { Alignments, CDropdownContext } from './CDropdown'
7
7
  import { CConditionalPortal } from '../conditional-portal'
8
8
 
9
9
  import type { Placements } from '../../types'
10
+ import { isRTL } from '../../utils'
10
11
 
11
12
  export interface CDropdownMenuProps
12
13
  extends HTMLAttributes<HTMLDivElement | HTMLUListElement>,
@@ -109,7 +110,7 @@ export const CDropdownMenu: FC<CDropdownMenuProps> = ({
109
110
  }
110
111
 
111
112
  if (direction === 'dropup') {
112
- _placement = 'top-start'
113
+ _placement = isRTL(dropdownMenuRef.current) ? 'top-end' : 'top-start'
113
114
  }
114
115
 
115
116
  if (direction === 'dropup-center') {
@@ -117,15 +118,15 @@ export const CDropdownMenu: FC<CDropdownMenuProps> = ({
117
118
  }
118
119
 
119
120
  if (direction === 'dropend') {
120
- _placement = 'right-start'
121
+ _placement = isRTL(dropdownMenuRef.current) ? 'left-start' : 'right-start'
121
122
  }
122
123
 
123
124
  if (direction === 'dropstart') {
124
- _placement = 'left-start'
125
+ _placement = isRTL(dropdownMenuRef.current) ? 'right-start' : 'left-start'
125
126
  }
126
127
 
127
128
  if (alignment === 'end') {
128
- _placement = 'bottom-end'
129
+ _placement = isRTL(dropdownMenuRef.current) ? 'bottom-start' : 'bottom-end'
129
130
  }
130
131
 
131
132
  const dropdownMenuComponent = (style?: React.CSSProperties, ref?: React.Ref<HTMLDivElement>) => (
@@ -112,7 +112,7 @@ export const CFormCheck = forwardRef<HTMLInputElement, CFormCheckProps>(
112
112
  if (inputRef.current && indeterminate) {
113
113
  inputRef.current.indeterminate = indeterminate
114
114
  }
115
- }, [indeterminate])
115
+ }, [indeterminate, inputRef.current])
116
116
 
117
117
  const FormControl = () => (
118
118
  <input
@@ -1,14 +1,15 @@
1
1
  import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
- import PropTypes from 'prop-types'
4
3
  import classNames from 'classnames'
5
- import { usePopper } from 'react-popper'
4
+ import PropTypes from 'prop-types'
6
5
  import { Transition } from 'react-transition-group'
6
+ import { createPopper, Instance, Placement } from '@popperjs/core'
7
7
 
8
8
  import { triggerPropType } from '../../props'
9
9
  import type { Triggers } from '../../types'
10
+ import { isRTL } from '../../utils'
10
11
 
11
- export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
12
+ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'> {
12
13
  /**
13
14
  * A string of all className you want applied to the component.
14
15
  */
@@ -49,6 +50,21 @@ export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
49
50
  visible?: boolean
50
51
  }
51
52
 
53
+ const getPlacement = (placement: string, element: HTMLDivElement | null): Placement => {
54
+ console.log(element)
55
+ switch (placement) {
56
+ case 'right': {
57
+ return isRTL(element) ? 'left' : 'right'
58
+ }
59
+ case 'left': {
60
+ return isRTL(element) ? 'right' : 'left'
61
+ }
62
+ default: {
63
+ return placement as Placement
64
+ }
65
+ }
66
+ }
67
+
52
68
  export const CPopover: FC<CPopoverProps> = ({
53
69
  children,
54
70
  className,
@@ -62,33 +78,53 @@ export const CPopover: FC<CPopoverProps> = ({
62
78
  visible,
63
79
  ...rest
64
80
  }) => {
81
+ const popoverRef = useRef(null)
82
+ const togglerRef = useRef(null)
83
+ const popper = useRef<Instance>()
65
84
  const [_visible, setVisible] = useState(visible)
66
- const popoverRef = useRef()
67
-
68
- const [referenceElement, setReferenceElement] = useState(null)
69
- const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
70
- const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
71
- const { styles, attributes } = usePopper(referenceElement, popperElement, {
72
- modifiers: [
73
- { name: 'arrow', options: { element: arrowElement } },
74
- {
75
- name: 'offset',
76
- options: {
77
- offset: offset,
78
- },
79
- },
80
- ],
81
- placement: placement,
82
- })
83
85
 
84
86
  useEffect(() => {
85
87
  setVisible(visible)
86
88
  }, [visible])
87
89
 
90
+ useEffect(() => {
91
+ if (_visible) {
92
+ initPopper()
93
+ }
94
+
95
+ return () => {
96
+ destroyPopper()
97
+ }
98
+ }, [_visible])
99
+
100
+ const initPopper = () => {
101
+ if (togglerRef.current && popoverRef.current) {
102
+ popper.current = createPopper(togglerRef.current, popoverRef.current, {
103
+ modifiers: [
104
+ {
105
+ name: 'offset',
106
+ options: {
107
+ offset: offset,
108
+ },
109
+ },
110
+ ],
111
+ placement: getPlacement(placement, togglerRef.current),
112
+ })
113
+ }
114
+ }
115
+
116
+ const destroyPopper = () => {
117
+ if (popper.current) {
118
+ popper.current.destroy()
119
+ }
120
+
121
+ popper.current = undefined
122
+ }
123
+
88
124
  return (
89
125
  <>
90
126
  {React.cloneElement(children as React.ReactElement<any>, {
91
- ref: setReferenceElement,
127
+ ref: togglerRef,
92
128
  ...((trigger === 'click' || trigger.includes('click')) && {
93
129
  onClick: () => setVisible(!_visible),
94
130
  }),
@@ -119,20 +155,20 @@ export const CPopover: FC<CPopoverProps> = ({
119
155
  <div
120
156
  className={classNames(
121
157
  'popover',
122
- `bs-popover-${placement.replace('left', 'start').replace('right', 'end')}`,
158
+ `bs-popover-${getPlacement(placement, togglerRef.current)
159
+ .replace('left', 'start')
160
+ .replace('right', 'end')}`,
123
161
  'fade',
124
162
  {
125
163
  show: state === 'entered',
126
164
  },
127
165
  className,
128
166
  )}
129
- ref={setPopperElement}
167
+ ref={popoverRef}
130
168
  role="tooltip"
131
- style={styles.popper}
132
- {...attributes.popper}
133
169
  {...rest}
134
170
  >
135
- <div className="popover-arrow" style={styles.arrow} ref={setArrowElement}></div>
171
+ <div data-popper-arrow className="popover-arrow"></div>
136
172
  <div className="popover-header">{title}</div>
137
173
  <div className="popover-body">{content}</div>
138
174
  </div>
@@ -1,14 +1,15 @@
1
- import React, { FC, HTMLAttributes, ReactNode, useEffect, useRef, useState } from 'react'
1
+ import React, { FC, HTMLAttributes, ReactNode, useRef, useEffect, useState } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
- import PropTypes from 'prop-types'
4
3
  import classNames from 'classnames'
5
- import { usePopper } from 'react-popper'
4
+ import PropTypes from 'prop-types'
6
5
  import { Transition } from 'react-transition-group'
6
+ import { createPopper, Instance, Placement } from '@popperjs/core'
7
7
 
8
8
  import { triggerPropType } from '../../props'
9
9
  import type { Triggers } from '../../types'
10
+ import { isRTL } from '../../utils'
10
11
 
11
- export interface CTooltipProps extends HTMLAttributes<HTMLDivElement> {
12
+ export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {
12
13
  /**
13
14
  * A string of all className you want applied to the component.
14
15
  */
@@ -18,7 +19,7 @@ export interface CTooltipProps extends HTMLAttributes<HTMLDivElement> {
18
19
  */
19
20
  content: ReactNode | string
20
21
  /**
21
- * Offset of the popover relative to its target.
22
+ * Offset of the tooltip relative to its target.
22
23
  */
23
24
  offset?: [number, number]
24
25
  /**
@@ -40,16 +41,31 @@ export interface CTooltipProps extends HTMLAttributes<HTMLDivElement> {
40
41
  */
41
42
  placement?: 'auto' | 'top' | 'right' | 'bottom' | 'left'
42
43
  /**
43
- * Toggle the visibility of popover component.
44
+ * Toggle the visibility of tooltip component.
44
45
  */
45
46
  visible?: boolean
46
47
  }
47
48
 
49
+ const getPlacement = (placement: string, element: HTMLDivElement | null): Placement => {
50
+ console.log(element)
51
+ switch (placement) {
52
+ case 'right': {
53
+ return isRTL(element) ? 'left' : 'right'
54
+ }
55
+ case 'left': {
56
+ return isRTL(element) ? 'right' : 'left'
57
+ }
58
+ default: {
59
+ return placement as Placement
60
+ }
61
+ }
62
+ }
63
+
48
64
  export const CTooltip: FC<CTooltipProps> = ({
49
65
  children,
50
66
  className,
51
67
  content,
52
- offset = [0, 0],
68
+ offset = [0, 6],
53
69
  onHide,
54
70
  onShow,
55
71
  placement = 'top',
@@ -57,33 +73,53 @@ export const CTooltip: FC<CTooltipProps> = ({
57
73
  visible,
58
74
  ...rest
59
75
  }) => {
60
- const tooltipRef = useRef()
76
+ const tooltipRef = useRef(null)
77
+ const togglerRef = useRef(null)
78
+ const popper = useRef<Instance>()
61
79
  const [_visible, setVisible] = useState(visible)
62
80
 
63
- const [referenceElement, setReferenceElement] = useState(null)
64
- const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
65
- const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
66
- const { styles, attributes } = usePopper(referenceElement, popperElement, {
67
- modifiers: [
68
- { name: 'arrow', options: { element: arrowElement } },
69
- {
70
- name: 'offset',
71
- options: {
72
- offset: offset,
73
- },
74
- },
75
- ],
76
- placement: placement,
77
- })
78
-
79
81
  useEffect(() => {
80
82
  setVisible(visible)
81
83
  }, [visible])
82
84
 
85
+ useEffect(() => {
86
+ if (_visible) {
87
+ initPopper()
88
+ }
89
+
90
+ return () => {
91
+ destroyPopper()
92
+ }
93
+ }, [_visible])
94
+
95
+ const initPopper = () => {
96
+ if (togglerRef.current && tooltipRef.current) {
97
+ popper.current = createPopper(togglerRef.current, tooltipRef.current, {
98
+ modifiers: [
99
+ {
100
+ name: 'offset',
101
+ options: {
102
+ offset: offset,
103
+ },
104
+ },
105
+ ],
106
+ placement: getPlacement(placement, togglerRef.current),
107
+ })
108
+ }
109
+ }
110
+
111
+ const destroyPopper = () => {
112
+ if (popper.current) {
113
+ popper.current.destroy()
114
+ }
115
+
116
+ popper.current = undefined
117
+ }
118
+
83
119
  return (
84
120
  <>
85
121
  {React.cloneElement(children as React.ReactElement<any>, {
86
- ref: setReferenceElement,
122
+ ref: togglerRef,
87
123
  ...((trigger === 'click' || trigger.includes('click')) && {
88
124
  onClick: () => setVisible(!_visible),
89
125
  }),
@@ -101,7 +137,6 @@ export const CTooltip: FC<CTooltipProps> = ({
101
137
  <Transition
102
138
  in={_visible}
103
139
  mountOnEnter
104
- nodeRef={tooltipRef}
105
140
  onEnter={onShow}
106
141
  onExit={onHide}
107
142
  timeout={{
@@ -114,20 +149,20 @@ export const CTooltip: FC<CTooltipProps> = ({
114
149
  <div
115
150
  className={classNames(
116
151
  'tooltip',
117
- `bs-popover-${placement.replace('left', 'start').replace('right', 'end')}`,
152
+ `bs-tooltip-${getPlacement(placement, togglerRef.current)
153
+ .replace('left', 'start')
154
+ .replace('right', 'end')}`,
118
155
  'fade',
119
156
  {
120
157
  show: state === 'entered',
121
158
  },
122
159
  className,
123
160
  )}
124
- ref={setPopperElement}
161
+ ref={tooltipRef}
125
162
  role="tooltip"
126
- style={styles.popper}
127
- {...attributes.popper}
128
163
  {...rest}
129
164
  >
130
- <div className="tooltip-arrow" style={styles.arrow} ref={setArrowElement}></div>
165
+ <div data-popper-arrow className="tooltip-arrow"></div>
131
166
  <div className="tooltip-inner">{content}</div>
132
167
  </div>
133
168
  )}
@@ -1,3 +1,4 @@
1
1
  import isInViewport from './isInViewport'
2
+ import isRTL from './isRTL'
2
3
 
3
- export { isInViewport }
4
+ export { isInViewport, isRTL }
@@ -0,0 +1,13 @@
1
+ const isRTL = (element?: HTMLElement | HTMLDivElement | null) => {
2
+ if (document.documentElement.dir === 'rtl') {
3
+ return true
4
+ }
5
+
6
+ if (element) {
7
+ return element.closest('[dir="rtl"]') !== null
8
+ }
9
+
10
+ return false
11
+ }
12
+
13
+ export default isRTL