Skip to content

Commit f88ed28

Browse files
committed
feat(ui-breadcrumb,ui-tooltip): add tooltips for truncated breadcrumbs
Closes: INSTUI-4371
1 parent 71416b3 commit f88ed28

File tree

5 files changed

+67
-29
lines changed

5 files changed

+67
-29
lines changed

packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/index.tsx

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ import { TruncateText } from '@instructure/ui-truncate-text'
2828
import { Link } from '@instructure/ui-link'
2929
import { omitProps } from '@instructure/ui-react-utils'
3030
import { testable } from '@instructure/ui-testable'
31+
import { Tooltip } from '@instructure/ui-tooltip'
3132

3233
import { propTypes, allowedProps } from './props'
33-
import type { BreadcrumbLinkProps } from './props'
34+
import type { BreadcrumbLinkProps, BreadcrumbLinkState } from './props'
3435

3536
/**
3637
---
@@ -40,7 +41,10 @@ id: Breadcrumb.Link
4041
**/
4142

4243
@testable()
43-
class BreadcrumbLink extends Component<BreadcrumbLinkProps> {
44+
class BreadcrumbLink extends Component<
45+
BreadcrumbLinkProps,
46+
BreadcrumbLinkState
47+
> {
4448
static readonly componentId = 'Breadcrumb.Link'
4549

4650
static propTypes = propTypes
@@ -52,26 +56,43 @@ class BreadcrumbLink extends Component<BreadcrumbLinkProps> {
5256
handleRef = (el: Element | null) => {
5357
this.ref = el
5458
}
59+
constructor(props: BreadcrumbLinkProps) {
60+
super(props)
5561

62+
this.state = {
63+
isTruncated: false
64+
}
65+
}
66+
handleTruncation(isTruncated: boolean) {
67+
if (isTruncated !== this.state.isTruncated) {
68+
this.setState({ isTruncated })
69+
}
70+
}
5671
render() {
5772
const { children, href, renderIcon, iconPlacement, onClick, onMouseEnter } =
5873
this.props
59-
74+
const { isTruncated } = this.state
6075
const props = omitProps(this.props, BreadcrumbLink.allowedProps)
6176

6277
return (
63-
<Link
64-
{...props}
65-
href={href}
66-
renderIcon={renderIcon}
67-
iconPlacement={iconPlacement}
68-
onClick={onClick}
69-
onMouseEnter={onMouseEnter}
70-
isWithinText={false}
71-
elementRef={this.handleRef}
72-
>
73-
<TruncateText>{children}</TruncateText>
74-
</Link>
78+
<Tooltip renderTip={children} preventTooltip={!isTruncated}>
79+
<Link
80+
{...props}
81+
href={href}
82+
renderIcon={renderIcon}
83+
iconPlacement={iconPlacement}
84+
onClick={onClick}
85+
onMouseEnter={onMouseEnter}
86+
isWithinText={false}
87+
elementRef={this.handleRef}
88+
>
89+
<TruncateText
90+
onUpdate={(isTruncated) => this.handleTruncation(isTruncated)}
91+
>
92+
<span aria-hidden={isTruncated}>{children}</span>
93+
</TruncateText>
94+
</Link>
95+
</Tooltip>
7596
)
7697
}
7798
}

packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/props.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,9 @@ const allowedProps: AllowedPropKeys = [
102102
'size'
103103
]
104104

105-
export type { BreadcrumbLinkProps }
105+
type BreadcrumbLinkState = {
106+
isTruncated: boolean
107+
}
108+
109+
export type { BreadcrumbLinkProps, BreadcrumbLinkState }
106110
export { propTypes, allowedProps }

packages/ui-tooltip/src/Tooltip/index.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ class Tooltip extends Component<TooltipProps, TooltipState> {
6464
placement: 'top',
6565
constrain: 'window',
6666
offsetX: 0,
67-
offsetY: 0
67+
offsetY: 0,
68+
preventTooltip: false
6869
} as const
6970

7071
private readonly _id: string
@@ -148,15 +149,16 @@ class Tooltip extends Component<TooltipProps, TooltipState> {
148149
positionTarget,
149150
onShowContent,
150151
onHideContent,
152+
preventTooltip,
151153
styles,
152154
...rest
153155
} = this.props
154156

155157
return (
156158
<Popover
157159
{...passthroughProps(rest)}
158-
isShowingContent={isShowingContent}
159-
defaultIsShowingContent={defaultIsShowingContent}
160+
isShowingContent={!preventTooltip && isShowingContent}
161+
defaultIsShowingContent={!preventTooltip && defaultIsShowingContent}
160162
on={on}
161163
shouldRenderOffscreen
162164
shouldReturnFocus={false}
@@ -177,10 +179,12 @@ class Tooltip extends Component<TooltipProps, TooltipState> {
177179
shouldCloseOnDocumentClick={false}
178180
shouldCloseOnEscape
179181
>
180-
<span id={this._id} css={styles?.tooltip} role="tooltip">
181-
{/* TODO: figure out how to add a ref to this */}
182-
{callRenderProp(renderTip)}
183-
</span>
182+
{!preventTooltip ? (
183+
<span id={this._id} css={styles?.tooltip} role="tooltip">
184+
{/* TODO: figure out how to add a ref to this */}
185+
{callRenderProp(renderTip)}
186+
</span>
187+
) : null}
184188
</Popover>
185189
)
186190
}

packages/ui-tooltip/src/Tooltip/props.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ type TooltipOwnProps = {
148148
event: React.UIEvent | React.FocusEvent,
149149
args: { documentClick: boolean }
150150
) => void
151+
152+
/**
153+
* If true, it won't display the tooltip. This is useful in cases when tooltip is conditionally needed
154+
* but in an uncontrolled way
155+
*/
156+
preventTooltip?: boolean
151157
}
152158

153159
type PropKeys = keyof TooltipOwnProps
@@ -177,6 +183,7 @@ type PropsPassableToPopover = Omit<
177183
| 'onFocus'
178184
| 'onBlur'
179185
| 'elementRef'
186+
| 'preventTooltip'
180187
>
181188

182189
type TooltipProps = PropsPassableToPopover &
@@ -211,7 +218,8 @@ const propTypes: PropValidators<PropKeys> = {
211218
offsetY: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
212219
positionTarget: PropTypes.oneOfType([element, PropTypes.func]),
213220
onShowContent: PropTypes.func,
214-
onHideContent: PropTypes.func
221+
onHideContent: PropTypes.func,
222+
preventTooltip: PropTypes.bool
215223
}
216224

217225
const allowedProps: AllowedPropKeys = [
@@ -230,7 +238,8 @@ const allowedProps: AllowedPropKeys = [
230238
'offsetY',
231239
'positionTarget',
232240
'onShowContent',
233-
'onHideContent'
241+
'onHideContent',
242+
'preventTooltip'
234243
]
235244

236245
export type {

packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/DesktopLayout/__new-tests__/TopNavBarDesktopLayout.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('<TopNavBarDesktopLayout />', () => {
7272

7373
describe('renderBreadcrumb', () => {
7474
it('should render breadcrumb container', () => {
75-
const { getByLabelText, getByText } = render(
75+
const { getByLabelText, getAllByText } = render(
7676
<TopNavBarContext.Provider
7777
value={{
7878
layout: 'desktop',
@@ -87,9 +87,9 @@ describe('<TopNavBarDesktopLayout />', () => {
8787
</TopNavBarContext.Provider>
8888
)
8989
const breadCrumbContainer = getByLabelText('You are here')
90-
const crumb1 = getByText('Course page 1')
91-
const crumb2 = getByText('Course page 2')
92-
const crumb3 = getByText('Course page 3')
90+
const crumb1 = getAllByText('Course page 1')[0]
91+
const crumb2 = getAllByText('Course page 2')[0]
92+
const crumb3 = getAllByText('Course page 3')[0]
9393

9494
expect(breadCrumbContainer).toBeInTheDocument()
9595
expect(crumb1).toBeVisible()

0 commit comments

Comments
 (0)