6
6
*/
7
7
8
8
import PropTypes from 'prop-types' ;
9
- import React , { useLayoutEffect , useState , ReactNode , useRef } from 'react' ;
9
+ import React , {
10
+ useLayoutEffect ,
11
+ useState ,
12
+ ReactNode ,
13
+ useRef ,
14
+ forwardRef ,
15
+ ForwardedRef ,
16
+ } from 'react' ;
10
17
import classNames from 'classnames' ;
11
18
import { useId } from '../../internal/useId' ;
12
19
import { usePrefix } from '../../internal/usePrefix' ;
@@ -17,6 +24,7 @@ import { Close } from '@carbon/icons-react';
17
24
import { Tooltip } from '../Tooltip' ;
18
25
import { Text } from '../Text' ;
19
26
import { isEllipsisActive } from './isEllipsisActive' ;
27
+ import mergeRefs from '../../tools/mergeRefs' ;
20
28
21
29
export interface DismissibleTagBaseProps {
22
30
/**
@@ -87,111 +95,117 @@ export type DismissibleTagProps<T extends React.ElementType> = PolymorphicProps<
87
95
DismissibleTagBaseProps
88
96
> ;
89
97
90
- const DismissibleTag = < T extends React . ElementType > ( {
91
- className,
92
- decorator,
93
- disabled,
94
- id,
95
- renderIcon,
96
- title = 'Dismiss' ,
97
- onClose,
98
- slug,
99
- size,
100
- text,
101
- tagTitle,
102
- type,
103
- ...other
104
- } : DismissibleTagProps < T > ) => {
105
- const prefix = usePrefix ( ) ;
106
- const tagLabelRef = useRef < HTMLDivElement > ( null ) ;
107
- const tagId = id || `tag-${ useId ( ) } ` ;
108
- const tagClasses = classNames ( `${ prefix } --tag--filter` , className ) ;
109
- const [ isEllipsisApplied , setIsEllipsisApplied ] = useState ( false ) ;
110
-
111
- useLayoutEffect ( ( ) => {
112
- const newElement = tagLabelRef . current ?. getElementsByClassName (
113
- `${ prefix } --tag__label`
114
- ) [ 0 ] ;
115
- setIsEllipsisApplied ( isEllipsisActive ( newElement ) ) ;
116
- } , [ prefix , tagLabelRef ] ) ;
117
- const handleClose = ( event : React . MouseEvent < HTMLButtonElement > ) => {
118
- if ( onClose ) {
119
- event . stopPropagation ( ) ;
120
- onClose ( event ) ;
121
- }
122
- } ;
123
-
124
- let normalizedDecorator = React . isValidElement ( slug ?? decorator )
125
- ? ( slug ?? decorator )
126
- : null ;
127
- if (
128
- normalizedDecorator &&
129
- normalizedDecorator [ 'type' ] ?. displayName === 'AILabel'
130
- ) {
131
- normalizedDecorator = React . cloneElement (
132
- normalizedDecorator as React . ReactElement < any > ,
133
- {
134
- size : 'sm' ,
135
- kind : 'inline' ,
98
+ const DismissibleTag = forwardRef (
99
+ < T extends React . ElementType > (
100
+ {
101
+ className,
102
+ decorator,
103
+ disabled,
104
+ id,
105
+ renderIcon,
106
+ title = 'Dismiss' ,
107
+ onClose,
108
+ slug,
109
+ size,
110
+ text,
111
+ tagTitle,
112
+ type,
113
+ ...other
114
+ } : DismissibleTagProps < T > ,
115
+ forwardRef : ForwardedRef < HTMLDivElement >
116
+ ) => {
117
+ const prefix = usePrefix ( ) ;
118
+ const tagLabelRef = useRef < HTMLDivElement > ( null ) ;
119
+ const tagId = id || `tag-${ useId ( ) } ` ;
120
+ const tagClasses = classNames ( `${ prefix } --tag--filter` , className ) ;
121
+ const [ isEllipsisApplied , setIsEllipsisApplied ] = useState ( false ) ;
122
+
123
+ useLayoutEffect ( ( ) => {
124
+ const newElement = tagLabelRef . current ?. getElementsByClassName (
125
+ `${ prefix } --tag__label`
126
+ ) [ 0 ] ;
127
+ setIsEllipsisApplied ( isEllipsisActive ( newElement ) ) ;
128
+ } , [ prefix , tagLabelRef ] ) ;
129
+ const combinedRef = mergeRefs ( tagLabelRef , forwardRef ) ;
130
+ const handleClose = ( event : React . MouseEvent < HTMLButtonElement > ) => {
131
+ if ( onClose ) {
132
+ event . stopPropagation ( ) ;
133
+ onClose ( event ) ;
136
134
}
135
+ } ;
136
+
137
+ let normalizedDecorator = React . isValidElement ( slug ?? decorator )
138
+ ? ( slug ?? decorator )
139
+ : null ;
140
+ if (
141
+ normalizedDecorator &&
142
+ normalizedDecorator [ 'type' ] ?. displayName === 'AILabel'
143
+ ) {
144
+ normalizedDecorator = React . cloneElement (
145
+ normalizedDecorator as React . ReactElement < any > ,
146
+ {
147
+ size : 'sm' ,
148
+ kind : 'inline' ,
149
+ }
150
+ ) ;
151
+ }
152
+
153
+ const tooltipClasses = classNames (
154
+ `${ prefix } --icon-tooltip` ,
155
+ `${ prefix } --tag-label-tooltip`
137
156
) ;
138
- }
139
157
140
- const tooltipClasses = classNames (
141
- `${ prefix } --icon-tooltip` ,
142
- `${ prefix } --tag-label-tooltip`
143
- ) ;
144
-
145
- // Removing onClick from the spread operator
146
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
147
- const { onClick, ...otherProps } = other ;
148
-
149
- const dismissLabel = `Dismiss "${ text } "` ;
150
-
151
- return (
152
- < Tag
153
- ref = { tagLabelRef }
154
- type = { type }
155
- size = { size }
156
- renderIcon = { renderIcon }
157
- disabled = { disabled }
158
- className = { tagClasses }
159
- id = { tagId }
160
- { ...otherProps } >
161
- < div className = { `${ prefix } --interactive--tag-children` } >
162
- < Text
163
- title = { tagTitle ? tagTitle : text }
164
- className = { `${ prefix } --tag__label` } >
165
- { text }
166
- </ Text >
167
- { slug ? (
168
- normalizedDecorator
169
- ) : decorator ? (
170
- < div className = { `${ prefix } --tag__decorator` } >
171
- { normalizedDecorator }
172
- </ div >
173
- ) : (
174
- ''
175
- ) }
176
- < Tooltip
177
- label = { isEllipsisApplied ? dismissLabel : title }
178
- align = "bottom"
179
- className = { tooltipClasses }
180
- leaveDelayMs = { 0 }
181
- closeOnActivation >
182
- < button
183
- type = "button"
184
- className = { `${ prefix } --tag__close-icon` }
185
- onClick = { handleClose }
186
- disabled = { disabled }
187
- aria-label = { title } >
188
- < Close />
189
- </ button >
190
- </ Tooltip >
191
- </ div >
192
- </ Tag >
193
- ) ;
194
- } ;
158
+ // Removing onClick from the spread operator
159
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
160
+ const { onClick, ...otherProps } = other ;
161
+
162
+ const dismissLabel = `Dismiss "${ text } "` ;
163
+
164
+ return (
165
+ < Tag
166
+ ref = { combinedRef }
167
+ type = { type }
168
+ size = { size }
169
+ renderIcon = { renderIcon }
170
+ disabled = { disabled }
171
+ className = { tagClasses }
172
+ id = { tagId }
173
+ { ...otherProps } >
174
+ < div className = { `${ prefix } --interactive--tag-children` } >
175
+ < Text
176
+ title = { tagTitle ? tagTitle : text }
177
+ className = { `${ prefix } --tag__label` } >
178
+ { text }
179
+ </ Text >
180
+ { slug ? (
181
+ normalizedDecorator
182
+ ) : decorator ? (
183
+ < div className = { `${ prefix } --tag__decorator` } >
184
+ { normalizedDecorator }
185
+ </ div >
186
+ ) : (
187
+ ''
188
+ ) }
189
+ < Tooltip
190
+ label = { isEllipsisApplied ? dismissLabel : title }
191
+ align = "bottom"
192
+ className = { tooltipClasses }
193
+ leaveDelayMs = { 0 }
194
+ closeOnActivation >
195
+ < button
196
+ type = "button"
197
+ className = { `${ prefix } --tag__close-icon` }
198
+ onClick = { handleClose }
199
+ disabled = { disabled }
200
+ aria-label = { title } >
201
+ < Close />
202
+ </ button >
203
+ </ Tooltip >
204
+ </ div >
205
+ </ Tag >
206
+ ) ;
207
+ }
208
+ ) ;
195
209
DismissibleTag . propTypes = {
196
210
/**
197
211
* Provide a custom className that is applied to the containing <span>
0 commit comments