66 */
77
88import 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' ;
1017import classNames from 'classnames' ;
1118import { useId } from '../../internal/useId' ;
1219import { usePrefix } from '../../internal/usePrefix' ;
@@ -17,6 +24,7 @@ import { Close } from '@carbon/icons-react';
1724import { Tooltip } from '../Tooltip' ;
1825import { Text } from '../Text' ;
1926import { isEllipsisActive } from './isEllipsisActive' ;
27+ import mergeRefs from '../../tools/mergeRefs' ;
2028
2129export interface DismissibleTagBaseProps {
2230 /**
@@ -87,111 +95,117 @@ export type DismissibleTagProps<T extends React.ElementType> = PolymorphicProps<
8795 DismissibleTagBaseProps
8896> ;
8997
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 ) ;
136134 }
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`
137156 ) ;
138- }
139157
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+ ) ;
195209DismissibleTag . propTypes = {
196210 /**
197211 * Provide a custom className that is applied to the containing <span>
0 commit comments